I want to write a easy Tagging-Plugin for cakephp3 applications. So lets say we have a model books and a model reviews. For each of this models it should be possible to attach Tags - just by adding a behavior (in a plugin): $this->addBehavior('Tag.Taggable').
I created two Tables in the Database: tags, tagged_tags.
Table tagged_tags:
id | tag_id | tagged_id |
1 | 1 | 1 |
2 | 2 | 1 |
tagged_id is the id of the tagged entity. The information which model it belongs to is in the other table. Table Tags:
id | tag | model |
1 | book | App\Model\Table\BooksTable |
2 | nobook | App\Model\Table\ReviewsTable|
Obviously, only the first Tag belongs to a book.
class TaggableBehavior extends Behavior
{
// Some more code here
public function __construct(Table $table, array $config = [])
{
parent::__construct($table, $config);
$this->_table = $table;
$this->_table->belongsToMany('Tag.Tags', [
'joinTable' => 'tagged_tags',
'foreignKey' => 'tagged_id',
'targetForeignKey' => 'tag_id',
'conditions' => [
'Tags.model' => get_class($this->_table);
]
]);
}
}
Retrieving the data works perfectly. But saving is an issue:
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'Tags.model' in 'where clause'
SQL Query:
SELECT TaggedTags.id AS
TaggedTags__id, TaggedTags.tagged_id ASTaggedTags__tagged_id, TaggedTags.tag_id ASTaggedTags__tag_idFROM tagged_tags TaggedTags WHERE (tagged_id = :c0 AND Tags.model = :c1)
I'm not so sure why cakephp performs a SELECT-query here, and I don't really care. Why this query causes an error is clear. But where is my mistake here? It has to do with the 'conditions' => ['Tags.model' => get_class($this->_table);. Without this, I can save data (but cant say which Tag belongs to a book or not)
EDIT: Some Additional Info
Here is the complete sql statement, displayed in the debug kit http://freetexthost.com/tc3s46nugi
controller code:
public function add()
{
$book = $this->Books->newEntity();
if ($this->request->is('post')) {
$book = $this->Books->patchEntity($book, $this->request->data);
if ($this->Books->save($book)) {
$this->Flash->success(__('The book has been saved.'));
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error(__('The book could not be saved. Please, try again.'));
}
}
In the Behavior I have some logic (copy/pasted) form the Bookmarks-tutorial
public function beforeSave($event, $entity, $options)
{
if ($entity->tag_string) {
$entity->tags = $this->_buildTags($entity->tag_string);
}
}
protected function _buildTags($tagString)
{
$new = array_unique(array_map('trim', explode(',', $tagString)));
$out = [];
$query = $this->_table->Tags->find()
->where([
'Tags.tag IN' => $new,
'Tags.model' => $this->name()
]);
// Remove existing tags from the list of new tags.
foreach ($query->extract('tag') as $existing) {
$index = array_search($existing, $new);
if ($index !== false) {
unset($new[$index]);
}
}
// Add existing tags.
foreach ($query as $tag) {
$tag['count'] = $tag['count']+1;
$out[] = $tag;
}
// Add new tags.
foreach ($new as $tag) {
$out[] = $this->_table->Tags->newEntity(['tag' => $tag, 'model' => $this->name(), 'count' => 0]);
}
return $out;
}