CakePHP 4.x : How to create a set of form controls that list all of the values in a HABTM (through) association? - has-and-belongs-to-many

I'm trying to create single edit form based on contacts that allows me to update/add skill levels for all the skills even if the contact doesn't already have a rating.
Something like:
Edit Contact
Name [TextBox: John Doe]
Skills
C# [TextBox: empty]
C/C++ [TextBox: empty]
Data Analysis [TextBox: 5]
Graphic Design [TextBox: empty]
Javascript [TextBox: 8]
Photography [TextBox: empty]
HTML [TextBox: empty]
Json [TextBox: empty]
Jquery [TextBox: empty]
My tables are configured like this:
Contacts
TABLE `contacts` (
`id` bigint(20) NOT NULL,
`name` varchar(100) NOT NULL
)
(1, 'John Doe')
$this->belongsToMany('Skills', [
'foreignKey' => 'contact_id',
'targetForeignKey' => 'skill_id',
'joinTable' => 'contacts_skills', (Tried with and without)
'through' => 'ContactsSkills', (Tried with and without)
]);
Skills
TABLE `skills` (
`id` bigint(20) NOT NULL,
`name` text NOT NULL
)
(1,'C#'),
(2,'C/C++'),
(3,'Data Analysis'),
(4,'Graphic Design'),
(5,'Javascript'),
(6,'Photography'),
(7,'HTML'),
(8,'Json'),
(9,'Jquery')
$this->belongsToMany('Contacts', [
'foreignKey' => 'skill_id',
'targetForeignKey' => 'contact_id',
'joinTable' => 'contacts_skills', (Tried with and without)
'through' => 'ContactsSkills', (Tried with and without)
]);
ContactsSkills
TABLE `contacts_skills` (
`id` bigint(20) NOT NULL,
`contact_id` bigint(20) NOT NULL,
`skill_id` bigint(20) NOT NULL,
`level` tinyint(1) DEFAULT NULL
)
(1,1,3,5),
(2,1,5,8)
$this->belongsTo('Contacts', [
'foreignKey' => 'contact_id',
'joinType' => 'INNER',
]);
$this->belongsTo('Skills', [
'foreignKey' => 'skill_id',
'joinType' => 'INNER',
]);
I've already dug though the documentation:
https://book.cakephp.org/4/en/orm/saving-data.html
https://book.cakephp.org/4/en/views/helpers/form.html (where I learned about _joinData) NOTE: The multiple select element doesn't
work for _joinData.
And many more. : )
I've tried just about every combination of associations to connect the three tables together, to no avail.
I know I can use _joinData to get the skills that already have a level:
echo $this->Form->control('skills.0._joinData.level');
echo $this->Form->control('skills.1._joinData.level', ['label' => ?????]);
though I'm having issues setting the label to the skill name.
My assumption is that I have to iterate the skills table and build the controls that way, but I don't know how to reference the join table from within the iteration.
Any suggestions?

Related

cannot add or update a child row a foreign key constraint fails laravel factory

I have users table with 20 rows.
When I factored user_transaction table, I see this error.
Illuminate\Database\QueryException with message 'SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fa
ils (webafra_testa.user_transaction, CONSTRAINT user_transaction_userid_foreign FOREIGN KEY (userId) REFERENCES users (id) ON DELETE CASCADE) (SQL: inse
rt into user_transaction (userId, price, description, type, traceableId, receptorId, status, paymentMethod, tracking_code, created_at, update d_at) values (0, 5, Voluptatem assumenda facilis perferendis nihil est., money_transfer, 1, 1, waiting_payment, online, 1, 2021-05-29 10:41:30, 2021-05-29 10:41:30
))'
factory
$factory->define(UserTransaction::class, function (Faker $faker) {
return [
'userId' => $faker->randomKey([1, 20]),
'price' => $faker->randomDigit,
'description' => $faker->sentence(5),
'type' => $faker->randomElement(['order', 'sub_factor', 'money_transfer', 'increase_inventory', 'application_fee']),
'traceableId' => $faker->randomKey([1000, 9999]),
'receptorId' => $faker->randomKey([1000, 9999]),
'status' => $faker->randomElement(['done', 'canceled', 'waiting_payment']),
'paymentMethod' => $faker->randomElement(['tesseke', 'cash', 'online']),
'tracking_code' => $faker->randomKey([1000, 9999]),
'created_at' => Carbon::now()->format('Y-m-d H:i:s'),
'updated_at' => Carbon::now()->format('Y-m-d H:i:s'),
];
});
'userId' => $faker->numberBetween(1, 20)
also for the others where you used randomKey , change it to numberbetween

Reset index on table with Eloquent model

I am scratching my head to figure out why this legacy code supposedly will reset the indexes on a database table. Can you shed some light on it?
$employee = new Employee();
$employee->update([
'recruited' => 0,
'job_id' => null,
'job_start_date' => null,
'job_end_date' => null,
'interviewed' => 0
]);
There are a corresponding Eloquent model called Employee.
try to use :
DB::table('employees')->update([
'recruited' => 0,
'job_id' => null,
'job_start_date' => null,
'job_end_date' => null,
'interviewed' => 0
]);

Creating a Phoenix table view with existing Hbase tables of common names

I've cloned a table and I'm trying to create a Phoenix view for it according to https://phoenix.apache.org/faq.html#How_I_map_Phoenix_table_to_an_existing_HBase_table.
Suppose I have two HBase tables below.
hbase(main):008:0> describe 'USERINFO'
Table USERINFO is ENABLED
USERINFO, {TABLE_ATTRIBUTES => {coprocessor$1 => '|org.apache.phoenix.coprocessor.ScanRegionObserver|805306366|', coprocessor$2 => '|org.apache.phoenix.coprocessor.UngroupedAggregateRegionObserver|805306366|', coprocessor$3 => '
|org.apache.phoenix.coprocessor.GroupedAggregateRegionObserver|805306366|', coprocessor$4 => '|org.apache.phoenix.coprocessor.ServerCachingEndpointImpl|805306366|', coprocessor$5 => '|org.apache.phoenix.hbase.index.Indexer|80530
6366|index.builder=org.apache.phoenix.index.PhoenixIndexBuilder,org.apache.hadoop.hbase.index.codec.class=org.apache.phoenix.index.PhoenixIndexCodec', coprocessor$6 => '|org.apache.hadoop.hbase.regionserver.LocalIndexSplitter|80
5306366|'}
COLUMN FAMILIES DESCRIPTION
{NAME => '0', DATA_BLOCK_ENCODING => 'FAST_DIFF', BLOOMFILTER => 'ROW', REPLICATION_SCOPE => '0', VERSIONS => '1', COMPRESSION => 'SNAPPY', MIN_VERSIONS => '0', TTL => 'FOREVER', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '6553
6', IN_MEMORY => 'false', BLOCKCACHE => 'true'}
hbase(main):006:0> describe 'USERPREFERENCE'
Table USERPREFERENCE is ENABLED
USERPREFERENCE, {TABLE_ATTRIBUTES => {coprocessor$1 => '|org.apache.phoenix.coprocessor.ScanRegionObserver|805306366|', coprocessor$2 => '|org.apache.phoenix.coprocessor.UngroupedAggregateRegionObserver|805306366|', coprocessor$
3 => '|org.apache.phoenix.coprocessor.GroupedAggregateRegionObserver|805306366|', coprocessor$4 => '|org.apache.phoenix.coprocessor.ServerCachingEndpointImpl|805306366|', coprocessor$5 => '|org.apache.phoenix.hbase.index.Indexer
|805306366|index.builder=org.apache.phoenix.index.PhoenixIndexBuilder,org.apache.hadoop.hbase.index.codec.class=org.apache.phoenix.index.PhoenixIndexCodec', coprocessor$6 => '|org.apache.hadoop.hbase.regionserver.LocalIndexSplit
ter|805306366|'}
COLUMN FAMILIES DESCRIPTION
{NAME => '0', DATA_BLOCK_ENCODING => 'FAST_DIFF', BLOOMFILTER => 'ROW', REPLICATION_SCOPE => '0', COMPRESSION => 'SNAPPY', VERSIONS => '1', TTL => 'FOREVER', MIN_VERSIONS => '0', KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '6553
6', IN_MEMORY => 'false', BLOCKCACHE => 'true'}
I would run the follow command to create a table view in Phoenix?
CREATE VIEW USERINFO ( pk VARCHAR PRIMARY KEY, "0".team VARCHAR, "0".firstname, "0".lastname )
CREATE VIEW USERPREFERENCE ( pk VARCHAR PRIMARY KEY, "0".firstname VARCHAR, "0".lastname )
This seems incorrect. How do I create a table view according to this situation?
So I believe what you are missing is that you need to specify what kind of a variable each column would be. So, technically you need to mention the type of column your variable firstname will be and so on.
I tried and tested this code. First I fired up my hbase-shell(Don't worry about the DCDR, its just my namespace, our DBA's have this pretty much locked down):
create 'DCDR:USERINFOV2', {NAME => '0', VERSIONS => 5}
create 'DCDR:USERPREFERENCEV2', {NAME => '0', VERSIONS => 5}
Then I fire up my phoenix shell:
CREATE VIEW "DCDR:USERINFOV2" ( pk VARCHAR PRIMARY KEY, "0".team VARCHAR, "0".firstname VARCHAR, "0".lastname VARCHAR);
CREATE VIEW "DCDR:USERPREFERENCEV2" ( pk VARCHAR PRIMARY KEY, "0".firstname VARCHAR, "0".lastname VARCHAR);
This gives me the views. Hopefully this works for you. Let me know if not.

Use sort order from TCA select to list items in fluid

In my TCA I use a select to an other table.
'modules' => [
'label' => 'LLL:EXT:myextension_module_table/Resources/Private/Language/locallang_db.xlf:tx_myextension_domain_model_semester.modules',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'enableMultiSelectFilterTextfield' => true,
'foreign_table' => 'tx_myextension_domain_model_module',
'minitems' => 1,
'maxitems' => 99,
],
],
Under "Selected Items" I can sort the items. Now I want to use this sort order in fluid. In database I see the right order 21,1,2,3,4,28. But in fluid the items are always sorted by uid.
Adding 'sortby' => 'sorting', with all needed changes doesn't solve my problem. In this case I can order records in list view. But I don't want this order. I want to show order from "Selected Items" in frontend.
In my ModuletableController.php I get the selected moduletable from Flexform.
/**
* action show
*
* #param \Vendor\Myextension\Domain\Model\Moduletable $moduletable
* #return void
*/
public function showAction(\Vendor\Myextension\Domain\Model\Moduletable $moduletable = NULL) {
if (is_null($moduletable)) {
$moduletable = $this->moduletableRepository->findByUid($this->settings['singleModuleTable']);
}
$this->view->assign('moduletable', $moduletable);
}
An in the template Show.html I loop through it.
<html xmlns:f="http://typo3.org/ns/TYPO3/Fluid/ViewHelpers">
<f:layout name="Default" />
<f:section name="main">
<h2>{moduletable.title}</h2>
<f:for each="{moduletable.semester}" as="semester">
<p>{semester.title}</p>
<f:for each="{semester.modules}" as="module">
<p{module.title}</p>
</f:for>
</f:for>
</f:section>
Solution is to use MM tables. Then sort order is set in TCA select field.
In TCA:
'modules' => [
'label' => 'LLL:EXT:myextension/Resources/Private/Language/locallang_db.xlf:tx_myextension_domain_model_semester.modules',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'enableMultiSelectFilterTextfield' => true,
'foreign_table' => 'tx_myextension_domain_model_module',
'foreign_sortby' => 'sorting',
'MM' => 'tx_myextension_semester_module_mm',
'minitems' => 1,
'maxitems' => 99,
],
],
In ext_tables.sql
CREATE TABLE tx_myextension_semester_module_mm (
uid_local int(11) unsigned DEFAULT '0' NOT NULL,
uid_foreign int(11) unsigned DEFAULT '0' NOT NULL,
sorting int(11) unsigned DEFAULT '0' NOT NULL,
sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL,
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);
How do you get the selected items in your Extbase controller? Maybe you can use this findByUidListOrderByList function here: http://blog.teamgeist-medien.de/2014/09/typo3-extbase-repository-find-by-multiple-uids-findbyuids.html#comment-90

cakephp 2.1 contain second level different field name

I can't get the values from a second level contain using different field name as link.
Asistencia model:
var $belongsTo = array('Employee');
Horario model:
var $belongsTo = array('Employee');
Employee model:
var $hasMany = array(
'Horario' => array(
'foreignKey' => 'employee_id',
'dependent' => true
),
'Asistencia' => array(
'foreignKey' => 'employee_id',
'dependent' => true
)
);
I'll explain using these values on my example record:
Asistencia: employee_id = 3701
Employee : id = 3701
In my find() from Asistencia, I get to contain Employee by switching Employee primaryKey just fine:
$this->Asistencia->Employee->primaryKey = 'id';
$this->paginate = array(
'contain' => array(
'Employee' => array(
'foreignKey' => 'employee_id',
//'fields' => array('id', h('emp_ape_pat'), h('emp_ape_mat'), h('name')),
'Horario' => array(
'foreignKey' => 'employee_id',
//'fields' => array('id' )
))
),
'conditions' => $conditions,
'order' => 'Asistencia.employee_id');
However, my Horario record is linked to Employees via another field: emp_appserial
Employee : emp_appserial = 373
Horario : employee_id = 373
How can my Employee array contain Horario array? (they do share the value just mentioned).
Currently, the Horario contain is using the value on Asistencia.employee_id and Employee.id (3701). (checked the sql_dump and is trying to get the Horario via
"WHERE `Horario`.`employee_id` = (3701)"
but for the Employee to contain Horario, it should use the value on Employee.emp_appserial and Horario.employee_id (373).
This is what i get (empty array at bottom)
array(
(int) 0 => array(
'Asistencia' => array(
'id' => '5',
'name' => null,
'employee_id' => '3701',
'as_date' => '2012-11-19',
),
'Employee' => array(
'id' => '3701',
'emp_appserial' => '373',
'emp_appstatus' => '8',
'AgentFullName' => '3701 PEREZ LOMELI JORGE LORENZO',
'FullNameNoId' => 'PEREZ LOMELI JORGE LORENZO',
'Horario' => array()
)))
Please notice:
'employee_id' => '3701', (Asistencia)
and
'emp_appserial' => '373', (Employee)
my Horario has 'employee_id' = 373.
How could I make the switch so the relation Employee<->Horario is based on emp_appserial?
Thank you in advance for any help.
Firstly you may be getting problems because you're using Spanish words for your Model names and I suspect you're also using them for Controllers.
This is a very bad practice since CakePHP's idea is:
Convention over configuration.
This is achieved through the Inflector class which "takes a string and can manipulate it to handle word variations such as pluralizations or camelizing and is normally accessed statically". But this works ONLY WITH English words. What this means for you is that you may be missing the data because Cake is not able to build the DB queries right, since you're using Spanish words. By doing so you're making the use of CakePHP's flexible persistence layer obsolete - you will have to make all the configurations by hand. Also most likely pagination will NOT WORK. Other parts of the framework may also not work properly:
HtmlHelper, FormHelper, Components, etc. ...
These problems will expand if you try to use more complex Model associations such as HABTM or "hasMany through the Join Model".
So I do not know why exactly you aren't seeing the Horario record, but what I just explained most likely is your problem.
What you're trying to do is against the core principles of CakePHP and you'd save yourself a lot of problems if you refactor a bit and use English words. Your other option is NOT TO use Cake.

Resources