CakePHP 4.1 ORM many-to-many contain works one way but not the other - cakephp

I'm facing an odd issue where a many-to-many relationship allows me to contain in one direction but not the other. I've managed to boil it down to a one-line example. In a controller,
this works as expected: $this->MarketplaceCategoryOptions->find()->contain(['Users'])->all()->toArray()
while this throws: $this->Users->find()->contain(['MarketplaceCategoryOptions'])->all()->toArray()
and the error is: Error: [RuntimeException] "_joinData" is missing from the belongsToMany results. Results cannot be created. in /application/vendor/cakephp/cakephp/src/ORM/Association/Loader/SelectWithPivotLoader.php on line 179.
Here are the models:
UsersTable.php
$this->belongsToMany('MarketplaceCategoryOptions')
->setThrough(MarketplaceCategoryOptionsUsersTable::class)
->setClassName('MarketplaceCategoryOptions')
->setForeignKey('user_id')
->setTargetForeignKey('marketplace_category_option_id');
and on the other side:
MarketplaceCategoryOptionsTable.php
$this->belongsToMany('Users')
->setThrough(MarketplaceCategoryOptionsUsersTable::class)
->setClassName('Users')
->setForeignKey('marketplace_category_option_id')
->setTargetForeignKey('user_id');
The join table:
MarketplaceCategoryOptionsUsersTable.php
$this->belongsTo('MarketplaceCategoryOptions')
->setForeignKey('marketplace_category_option_id')
->setJoinType('INNER');
$this->belongsTo('Users')
->setForeignKey('user_id')
->setJoinType('INNER');
The setup appears fully symmetrical to me so I really don't understand how it's possible for these two to behave differently. As far as I'm aware I'm following all cake conventions. I also have a bunch of other many-to-many associations just like this one in the same codebase and they work fine. I've also manually removed all cache from the tmp directory, just in case. Any ideas what could be the reason?

Related

Grails 3 "show" view with Fields plugin 2.1.0-SNAPSHOT

Stuck at a trivial problem in Grails 3.1.5: Show the fields of a domain object, excluding one of them, including a transient property. Yes, this is my first Grails 3 project after many years with previous versions.
The generated show.gsp contains
<f:display bean="rfaPdffile"/>
This will include a field that may contain megabytes of XML. It should never be shown interactively. The display: false constraint is no longer in the docs, and seems to be silenty ignored.
Next I tried explicitly naming the fields:
<f:with bean="rfaPdffile">
<f:display property='fileName'/>
<f:display property='pageCount'/>
...
</f:with>
This version suprisingly displays the values without any markup whatsoever. Changing display to field,
<f:with bean="rfaPdffile">
<f:field property='fileName'/>
<f:field property='pageCount'/>
...
</f:with>
sort of works, but shows editable values. So does f:all.
In addition I tried adding other attributes to f:display: properties (like in f:table), except (like in f:all). I note in passing that those two attributes have different syntax for similar purposes.
In the Field plugin docs my use case is explicitly mentioned as a design goal. I must have missed something obvious.
My aim is to quickly throw together a prototype gui, postponing the details until later. Clues are greatly appreciated
If I understood you correctly, you want to have all bean properties included in the gsp but the one with the "megabytes of XML" should not be displayed to the user?
If that is the case you can do:
f:with bean="beanName"
f:field property="firstPropertyName"
f:field property="secondPropertyName"
And the one you don't wish to display:
g:hiddenField name="propertyName" value="${beanName.propertyName?}"
f:with
So list all the properties as f:field or f:display and put the one you don't wish to display in a g:hiddenField Grails tag
You can also try:
f:field property="propertyName"
widget-hidden="true"
but the Label is not hidden in this case.
Hope it helps
My own answer: "use the force, read the source". The f:display tag has two rather obvious bugs. I will submit a pull request as soon as I can.
Bugs aside, the documentation does not mention that the plugin may pick up the "scaffold" static property from the domain, if it has one. Its value should be a map. Its "exclude" key may define a list of property names (List of String) to be excluded. This probably works already for the "f:all" tag; bug correction is needed for the "f:display" tag.
My subjective impression is that the fields plugin is in a tight spot. It is intertwined with the Grails architecture, making it sensitive to changes in Grails internals. It is also required by the standard scaffolding plugin, making it very visible. Thus it needs constant attention from maintainers, a position not to be envied. Even now conventions for default constraints seem to have changed somewhere between Grails 3.0.9 and 3.1.7.
Performance of the fields plugin is sensitive to the total number of plugins in the app where it is used. It searches all plugins dynamically for templates.
For the wish list I would prefer stricter tag naming. The main tags should be verbs. There are two main actions, show and edit. For each action there are two main variants, single bean or multiple beans.
My answer is that at present (2 March 2017) there is no answer. I have searched the Net high and low. For the index (list) and create and edit views, the fields plugin works well enough. A certain field can be easily excluded from the create and edit views, relatively easily from the list view (by listing those that should show), and in no way I could find from the show view. This is such a common need that one would suspect it will be addressed soon. Also, easily showing derived values in the show view, like 'total' for an invoice. One can do that by adding an ordered list with a list item showing the value below the generated ordered list of values, but that is kind of a hack.
In some ways, the old way was easier. Yes, it generated long views, but they were generated and didn't have to be done by the programmer - just custom touches here and there.

Cakephp 3.0 bake error with foreign keys generated by Migrations

I tried to bake my MVC in cakephp 3. I used Migrations to create my table including the foreign keys. Here's the code I use for migrations
{
$utTable = $this->table('unit_types');
$utTable
->addColumn('unit_type_desc','string')
->addColumn('created','datetime')
->addColumn('modified','datetime')
->create();
$mlTable = $this->table('master_lists');
$mlTable
->addColumn('unit_type_id','integer')
->addForeignKey('unit_type_id', 'unit_types', 'id')
->addColumn('created','datetime')
->addColumn('modified','datetime')
->create();
}
It prompts no error and seems to work well. but when I bake the master lists I got some errors, I saw '0 columns' phrase or something like that and a few more.
Now my question is, is this the proper way to put data association in cakephp? or should I be using my model files? I have no idea. I'm new to this framework. Thanks for any answers.
I don't know if you solve your problem, but I would suggest you to use save() instead of create().
For anyone searching the same topic, Cakephp 3 use the library Phinx to handle migrations, here is info about your problem: http://docs.phinx.org/en/latest/migrations.html#working-with-foreign-keys

Missing Model/Database Table in a Plugin in a CakePHP project

I have a CakePHP project having 3 plugins: Plugin1, Plugin2, Plugin3. These are simple plugins, I've just tried to split up my project into 3 smaller & easier parts.
Plugin1 has to use a model Model1, where there is no db table for this Model1.
I know the $useTable=false thing. My problem is, I have a function func() in that model, and I am trying to use that function func() in a Controller, in Plugin1. But it shows an SQL SYNTAX error. I think, usually we use the find() function of the model, but here we are creating a function and using, may be that's the problem.
Here, table-name and Model-name are in correct convention. I don't want to create a table for this, since I don't need it. What to do now ?

Creating plugin for Django-CMS

I think I screwed up somewhere while trying to create a django-cms plugin and now I am unable to go back. The plugin (called sbbplugin) seems to be "working" (it gets displayed) but whenever I try to publish the site I get the following error:
DatabaseError at /admin/cms/page/18/publish/
relation "cmsplugin_sbbmodel" does not exist
LINE 1: ...id", "cmsplugin_sbbmodel"."cmsplugin_ptr_id" FROM "cmsplugin...
^
I tried removing the plugin but I can not do it because the page is not published. My plugin has no models or anything. Also I'm unable to remove the plugin from the page by clicking delete. Seems like the database is broken. Since I am not experienced enough to know what information you need I would appreciate it, if you could give me further instructions on what I should do.
Update: I think the problem is that I tried to use a model (sbbmodel) which I deleted. Should I try to add the model again? If so what should I do to fix the database? Do I have to run a migration for my app? Do I even have to register the plugin as an app?
Update2: So I tried to add the model again and migrate the app but I get the following error:
CommandError: One or more models did not validate:
sbbplugin.sbbmodel: Accessor for field 'cmsplugin_ptr' clashes with related field 'CMSPlugin.sbbmodel'. Add a related_name argument to the definition for 'cmsplugin_ptr'.
sbbplugin.sbbmodel: Reverse query name for field 'cmsplugin_ptr' clashes with related field 'CMSPlugin.sbbmodel'. Add a related_name argument to the definition for 'cmsplugin_ptr'.
S.sbbmodel: Accessor for field 'cmsplugin_ptr' clashes with related field 'CMSPlugin.sbbmodel'. Add a related_name argument to the definition for 'cmsplugin_ptr'.
S.sbbmodel: Reverse query name for field 'cmsplugin_ptr' clashes with related field 'CMSPlugin.sbbmodel'. Add a related_name argument to the definition for 'cmsplugin_ptr'.
which I do not really understand because I never specified a foreign key. I assume it is because I inherit from CMSPlugin. Any help?
I finally fixed it. First I removed every file that I created for my plugin. Then I deleted every instance of my plugin that was saved in the database. Unfortunately that did not solve the problem and I was still getting the same error even after restarting the server.
What did the trick was to go into phpPgAdmin and select all tables than choose "correct". It did not tell me what exactly the issue was but afterwards everything was back to normal again. So if you somehow run into the same problem, just "correct" your database automatically.

How to choose the test DB cakePHP testing

I am using the cake bake command to create my fixture and Test models. I have done it successfully with one of my models but i am having troubles with another one.
I dont know why, it tries to work with the default DB instead with the test one that i have already defined and that is the one used by the first model i tested.
Both Test models, the one which works well and this 2nd one which doesn't, have been created with cake bake and they look exactly the same.
Both of them have this in their fixture class:
public $import = array('records' => true, 'connection' => 'test');
Test defines the connection to my test database.
What can be the problem?
I have experiment something weird. In my test model if i use the singluar instead of the plural, it works with the default DB BUT if i change it to plural it works with the test DataBase. (but it can not call any method from the model, just defined methods such as find()).
For example:
public function setUp() {
parent::setUp();
//uses the default DB (i dont know why)
$this->Post = ClassRegistry::init('Post');
print_r($this->User->find('all'));
$this->Post->updatePostsStatus(1); //works well
//uses the default Test Database (i dont know why either)
$this->Post = ClassRegistry::init('Posts');
print_r($this->User->find('all'));
$this->Post->updatePostsStatus(1); //doesn't work. No function found.
/*SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updatePostsStatus' at line 1*/
}
Thanks.
I HAVE UPDATED IT.
LAST UPDATE AND SOLUTION
The problem was the bad initialization of the Post Model constructor.
Using the plural was not relevant. It is like using any non existent model. It will always get the test database.
The answer for the good initialization of the constructor is here:
How to override model's constructor correctly in CakePHP
That sort of MySQL error can be very misleading in CakePHP.
Basically the actual problem is with the line $this->Post->updatePostsStatus(1); Specifically it's that eitherthe Post model or updatePostsStatus() method of the posts model doesn't exist.
You basically have a typo on this line, check the method name. Should it be updatePostStatus? (no plural on posts?)
For some reason, if you try to call an undefined method on a model in CakePHP, instead of reporting the method is not found, the "automagicness" of CakePHP kicks in and bizarrely it tries to push the unfound method name straight into MySQL.
Basically, because CakePHP cannot find the $this->Post->updatePostsStatus(1); method, it is actually trying to execute a MySQL query of updatePostsStatus ie mysql_query("updatePostsStatus');, which is why we are seeing this wierd database error. Chances are you've just misspelled the method name.
If the method name is still not the problem, it's something to do with the Posts model not being instantiated correctly. Although that seems doubtful as long as you got all the names right. It is definitely not a database issue, just Cakes lame way of handling unknown class methods automagically.

Resources