Cakephp 3 + CounterCache with prefixed plugin - cakephp-3.0

Let's assume we have two models:
Foo/Blog.posts and Foo/Blog.categories.
I want to update categories.post_count fields each time a new post is created.
I attached the CounterCache behavior in Foo\Blog\Model\Table\PostsTable like this (in my initialize func):
$this->addBehavior('CounterCache', [
'Categories' => ['post_count']
]);
But when I try to add a new post, I have an error like this:
Base table or view not found: 1146 Table 'my-db.categories' doesn't exist
Any idea?
Tx for your help

I found what it doesn't work.
My relationships between Foo/Blog.posts and Foo/Blog.categories was incorrect. I forgot the plugin prefix.
The correct relationships declaration:
$this->belongsTo('Categories', [
'className' => 'Foo/Blog.Categories'
]);

Related

Laravel Eloquent create method skipping IDs and returning them

I'm simply trying to create a new model through Eloquent's create method. However, we're seeing that the new model will sometimes contain an id that has been skipped in mysql. This id is designated as an autoincrement column in mysql.
We are also seeing this happen when using DB:getPdo()->lastInsertId() immediately after the model has been created.
$questionResponse = $response->questionResponses()->create([
'survey_question_id' => $question->id,
'response' => 5
]);
$response_id = $questionResponse->id; // weve also used DB::getPdo()->lastInsertId();
Queue::push('TheWorker', array('survey_id' => $survey->id, 'response_id' => $response_id, 'question_id' => $question->id, 'user_id' => $user->id, 'student_target_id' => $student_target_id));
TheWorker then fails due to receiving an incorrect $repsonse_id that doesn't exist in the DB.
We would expect that the newly created model would contain the id that matches the record created in mysql.
Thanks in advance!

CakePHP 3: Best Practice for Temporary SQL Tables

Dear CakePHP 3 developers,
I'd like to use SQL's Temporary Tables in a CakePHP 3.4.13 project for a single run through a script. Going through Cake's documentation, there seems no direct way to tell CakePHP my desire. How would I best go about it, then?
I've prepared a Table in src/Model/Table/TempItemsTable.php:
namespace App\Model\Table;
use Cake\ORM\Table;
class TempItemsTable extends Table
{
public $fields = [
'id' => ['type' => 'integer'],
'con' => ['type' => 'string', 'length' => 255, 'null' => false],
'_constraints' => [
'primary' => ['type' => 'primary', 'columns' => ['id']]
]
];
public function initialize(array $config)
{
// $this->setTable(null);
}
}
The idea to use $fields to tell CakePHP the desired table schema comes from a possibly unrelated documentation for Test Fixtures.
But how do I tell CakePHP not to look for an actual table in the database?
The uncommented line $this->setTable(null); was my poor attempt at that, which is supposedly similiar to the right way in earlier versions of CakePHP, but according to version 3.x documentation, setTable() doesn't accept null, while table() does, but it's deprecated as of 3.4 and also didn't change anything.
Finally, of course, I get this exception as soon as I try to access this "table" in a controller via $temp = TableRegistry::get('TempItems');:
SQLSTATE[42S02]: Base table or view not found: 1146 Table 'mydatabase.temp_items' doesn't exist
Help, I'm stuck. :(
There's no need to tell it to not look for the table, actually that's the opposite of what you want to do, given that you eventually want to access it.
The table class should basically be configured as usual, and you should create the temporary database table before the application causes it to be accessed. You can either write the raw table creation SQL manually, or generate it from a \Cake\Database\Schema\TableSchema instance, which supports temporary tables.
You can either explicitly create the schema object:
$schema = new \Cake\Database\Schema\TableSchema('temp_items');
$schema
->addColumn('id', ['type' => 'integer'])
->addColumn('con', ['type' => 'string', 'length' => 255, 'null' => false])
->addConstraint('primary', ['type' => 'primary', 'columns' => ['id']])
->setTemporary(true);
$TableObject->setSchema($schema);
or let the table object generate it, using your fields definition array:
$TableObject->setSchema($TableObject->fields);
$schema = $TableObject->getSchema()->setTemporary(true);
You can then generate the table creation SQL from the schema object and run it against the database:
$connection = $TableObject->getConnection();
$queries = $schema->createSql($connection);
$connection->transactional(
function (\Cake\Database\Connection $connection) use ($queries) {
foreach ($queries as $query) {
$stmt = $connection->execute($query);
$stmt->closeCursor();
}
}
);
$queries would be an array of SQL commands required to create the table, something along the lines of:
[
'CREATE TEMPORARY TABLE `temp_items` (
`id` INTEGER AUTO_INCREMENT,
`con` VARCHAR(255) NOT NULL,
PRIMARY KEY (`id`)
)'
]
Note that if you do not assign the schema to the table object, you could run into caching problems, as the cached schema wouldn't match anymore when you change the table definition and do not clear the cache.
See also
Cookbook > Database Access & ORM > Schema System
Cookbook > Database Access & ORM > Database Basics

CakePHP 3.x UnitTest "Base table or view not found"

I get an Error-Message in a UnitTest in CakePHP 3.2 and the official documentation doesn't help me here anymore. I think the error has something todo with the SQL-Joins I try to use.
The Error-Message is the following:
`1) App\Test\TestCase\Controller\GetContentControllerTest::testIndex
PDOException: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'contentmapper_test.CmDeviceclasses' doesn't exist`
In my Testclass GetContentControllerTest I load my fixtures that I need and that creates my Database-Tables on start:
`public $fixtures = [
'app.cm_content_options',
'app.cm_content_addresses',
'app.cm_deviceclasses',
'app.cm_properties'
];`
In the setUp()-Method I load the Main-Table:
`public function setUp()
{
parent::setUp();
$this->CmContentOptions = TableRegistry::get('CmContentOptions');
}`
My Test-Method testIndex() looks like this:
public function testIndex()
{
//find the belonging ContentOption to address data
//submitted by the client
$this->testFindAllByUriAndDeviceclassAndBoxId();
assert($this->arrObjContentOptions->count() == 1);
}
The testFindAllByUriAandDeviceclassAndBoxID() looks like shown in the following Image (the Editor is not able to prettyprint it correctly):
testFindAllByUriAandDeviceclassAndBoxID()
It's hard to describe the whole Context; I hope it is possible to understand.
The Error happens exactly on the statement shown in the image:
$result = $query->toArray()
I think I just forgot something to add in the setUp() Method or something like that.
I hope anyone can help.
You joins are set up incorrectly, you're mixing up aliases and table names.
The alias is the key of the join array, and the table key should hold the actual database table name, not the table class name.
Given that you are following CakePHPs naming conventions for your database table names, your join setup should look more like this
[
'CmDeviceclasses' => [ /* < this is the SQL alias */
'table' => 'cm_deviceclasses', /* < this is the database table name */
'type' => 'LEFT',
'conditions' => [
'CmDeviceclasses.classname' => $this->deviceclass
]
],
'CmContentAddresses' => [
'table' => 'cm_content_addresses',
'type' => 'INNER',
'conditions' => [
'CmContentAddresses.uri' => $this->uri,
'CmContentAddresses.boxid' => $this->boxid,
]
],
],
[
'CmDeviceclasses.classname' => 'string',
'CmContentAddresses.uri' => 'string',
'CmContentAddresses.boxid' => 'string'
]
There is no technical need to follow the CamelCase conventions for the aliases, but for sure it doesn't hurt to generally stick to the conventions.
ps, if you setup the associations properly, then there should be no need to use manual joins, you could just use Query::contain() and Query::innerJoinWith() or Query::matching().
See
Cookbook > Database Access & ORM > Associations - Linking Tables Together
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Retrieving Associated Data
Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Filtering by Associated Data

yii2 and select2 for tags

I'm using kartik select2 widget like this:
echo Select2::widget([
'model' => $model,
'attribute' => 'script_tags',
'data' => $model->tagList,
'options' => ['multiple' => true,'placeholder' => 'Select states ...'],
'pluginOptions' => [
'tags' => true
],
]);
$model->tagList is array in this format ['id'=>'name'] populated from database.
My question is what is the best why to save it into db table because with custom tags i have response for script_tags like this
[0=>'1', 1=>'5', 2=>'math'],
i need to save new tag in table tag[fields=id, name] and relations for all tags in table tagmap[fields=id, script_id, tag_id]
should i check if is integer save relations in tagmap, if is string save first new tag in tag table then save relation in tagmap
Your approach looks fine, except when somebody explicitly enters a number as tag value. Let's imagine in your tag table ID 1 is 'foo' and ID 5 is 'bar'. There's no way to tell if you got a 'foo,bar,math' or 'foo,5,math'. You could check if those IDs actually exist in your database and act accordingly, but that's probably overkill.
However, I suggest you take a look at some tagging solutions that already exist. I'm pretty happy with 2amigos/yii2-taggable-behavior, but there's also creocoder/yii2-taggable and probably many others.
As an added benefit, 2amigos taggable also stores tag frequencies.
Keep in mind that having an id column in tagmap, which I bet is an autoincrement PK, is bad practice, you should use (script_id, tag_id) as a composite PK.

UPDATED: Magento add customer attribute filter to order grid

I have extended the Mage_Adminhtml_Block_Sales_Order_Grid class with a custom module to add several customer attributes (Magento EE 1.10) to the grid.
Two of the attributes I added are text fields (i.e. they live in the customer_entity_varchar table, and I was able to add them to the collection and display them in the grid. So far so good.
A third attribute is a select, so the values live in the customer_entity_int, the eav_attribute_option and the eav_attribute_option_value tables. I added the necessary values to the collection (using $collection->getSelect()->joinLeft(.....). Again, so far so good.
My problem is being able to display and filter the attribute at the same time.
Inside the _prepareColumns() function in my MyCompany_MyModule_Block_Adminhtml_Order_Grid class, if I add a column like this, - as expected - I can display the values of the attribute on each row, but I don't get a drop down filter in the header:
protected function _prepareColumns()
{
...
$this->addColumn('bureau', array(
'header' => Mage::helper('sales')->__('Bureau'),
'index' => 'bureau',
'type' => 'text'
));
...
}
Following the example of status, and adding the column like this, gives me the drop down filter in the header, but it no longer displays the values for the attribute in each row:
protected function _prepareColumns()
{
...
$this->addColumn('bureau', array(
'header' => Mage::helper('sales')->__('Bureau'),
'index' => 'bureau',
'type' => 'options',
'options' => $this->_getBureauOptions(),
'filter_index' => 'value_option_table.option_id'
));
...
}
protected function _getBureauOptions()
{
$bureau = Mage::getResourceModel('eav/entity_attribute_collection')
->setCodeFilter('bureau')
->getFirstItem();
$bureauOptions = $bureau->getSource()->getAllOptions(false);
$optionsArr = array();
foreach ($bureauOptions as $option) {
$optionsArr[$option['value']] = $option['label'];
}
return $optionsArr;
}
Any advice / explanation would be much appreciated.
UPDATE:
It turns out that my code also causes a SQL error in a multi-website environment when an admin user only has permissions for some websites:
"SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'store_id' in where clause is ambiguous"
#clockworkgeek had the answer to the first part of my question.
The problem was that my joinLeft() was retrieving text values from the attribute options, while I should have been retrieving integer values when using 'type => 'options'.
Once I changed my joinLeft() to only retrieve integer values from customer_entity_int (actually a simpler join), the filtering and display worked flawlessly - thank you sir.
I will re-post my second issue (about SQL errors caused by permissions) as a separate question.