Update Validation Rules for Articles: src/Model/Table/ArticlesTable.php
class ArticlesTable extends Table { public function initialize(array $config): void { $this->addBehavior('Timestamp'); } public function beforeSave(EventInterface $event, $entity, $options) { if ($entity->isNew() && !$entity->slug) { $sluggedTitle = Text::slug($entity->title); // trim slug to maximum length defined in schema $entity->slug = substr($sluggedTitle, 0, 191); } } public function validationDefault(Validator $validator): Validator { $validator ->notEmptyString('title') ->minLength('title', 10) ->maxLength('title', 255) ->notEmptyString('body') ->minLength('body', 10); return $validator; } }
Add Delete Action
L src/Controller/ArticlesController.php
public function delete($slug){ $this->request->allowMethod(['post', 'delete']); $article = $this->Articles->findBySlug($slug)->firstOrFail(); if($this->Articles->delete($article)){ $this->Flash->success(__('The {0} article has been deleted.', $article->title)); return $this->redirect(['action'=> 'index']); } }
templates/Articles/index.php
<td> <?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?> <?= $this->Form->postLink( 'Delete', ['action' => 'delete', $article->slug], ['confirm' => 'Are you sure?']) ?> </td>
### Tags and User
$ bin/cake bake model users
$ bin/cake bake controller users
$ bin/cake bake template users
$ bin/cake bake all tags
Updating Articles to enable Tagging
// in src/Controller/ArticlesController.php
public function add() { $article = $this->Articles->newEmptyEntity(); if ($this->request->is('post')) { $article = $this->Articles->patchEntity($article, $this->request->getData()); // Hardcoding the user_id is temporary, and will be removed later // when we build authentication out. $article->user_id = 1; if ($this->Articles->save($article)) { $this->Flash->success(__('Your article has been saved.')); return $this->redirect(['action' => 'index']); } $this->Flash->error(__('Unable to add your article.')); } $tags = $this->Articles->Tags->find('list')->all(); $this->set('tags', $tags); $this->set('article', $article); }
templates/Articles/add.php:
echo $this->Form->control('tags._ids', ['options' => $tags]);
public function edit($slug) { $article = $this->Articles ->findBySlug($slug) ->contain('Tags') ->firstOrFail(); if($this->request->is(['post', 'put'])){ $this->Articles->patchEntity($article, $this->request->getData()); if ($this->Articles->save($article)){ $this->Flash->success(__('Your article has been updated.')); return $this->redirect(['action' => 'index']); } $this->Flash->error(__('Unable to update your article.')); } $tags = $this->Articles->Tags->find('list')->all(); $this->set('tags', $tags); $this->set('article', $article); }
Finding Articles by Tags
// config/routes.php
$builder->scope('/articles', function(RouteBuilder $builder){ $builder->connect('/tagged/*', ['controller' => 'Articles', 'action'=>'tags']); });
src/Controller/ArticlesController.php
public function tags(){ $tags = $this->request->getParam('pass'); $articles = $this->Articles->find('tagged', [ 'tags' => $tag ]) ->all(); $this->set([ 'articles' => $articles, 'tags' => $tags ]): }
Creating the Finder Method
// src/Model/Table/ArticlesTable.php
public function findTagged(Query $query, array $options){ $columns = [ 'Articles.id', 'Articles.user_id', 'Articles.title', 'Articles.body', 'Articles.published', 'Articles.created', 'Articles.slug', ]; $query = $query ->select($columns) ->distinct($columns); if(empty($options['tags'])){ $qeury->leftJoinWith('Tags') ->where(['Tags.title IS' => null]); } else { $query->innerJoinWith('Tags') ->where(['Tags.title IN' => $options['tags']]); } return $query->group(['Articles.id']); }
Creating the View
// templates/Articles/tags.php
<h1> Articles tagged with <?= $this->Text->toList(h($tags), 'or') ?> </h1> <section> <?php foreach($articles as $article): ?> <h4><?= $this->Html->link( $article->title, ['controller' => 'Articles', 'action' => 'view', $article->slug] ) ?></h4> <span><?= h($article->created) ?></span> <?php endforeach; ?> </section>
Improving the Tagging Experience
– Adding a computed field
// src/Model/Entity/Article.php
protected function _getTagString(){ if(isset($this->_fields['tag_string'])){ return $this->_fields['tag_string']; } if(empty($this->tags)){ return ''; } $tags = new Collection($this->tags); $str = $tags->reduce(function ($string, $tag){ return $string . $tag->title . ', '; }, ''); return trim($str, ', ') }
Updating the views
// templates/Articles/add.php
echo $this->Form->control('tag_string', ['type' => 'text']);
// templates/Articles/view.php
<h1><?= h($article->title) ?></h1> <p><?= h($article->body) ?></p> <p><b>Tags:</b><?= h($article->tag_string) ?></p>
public function view($slug) { $article = $this->Articles->findBySlug($slug)->contain('Tags')->firstOrFail(); $this->set(compact('article')); }
Persisting the Tag String
// src/Model/Table/ArticlesTable.php
public function beforeSave(EventInterface $event, $entity, $options) { if($entity->tag_string) { $entity->tags = $this->_buildTags($entity->tag_string); } if ($entity->isNew() && !$entity->slug) { $sluggedTitle = Text::slug($entity->title); // trim slug to maximum length defined in schema $entity->slug = substr($sluggedTitle, 0, 191); } } protected function _buildTags($tagString){ $newTags = array_map('trim', explode(',', $tagString)); $newTags = array_filter($newTags); $newTags = array_unique($newTags); $out = []; $tags = $this->Tags->find() ->where(['Tags.title IN' => $newTags]) ->all(); foreach ($tags->extract('title') as $existing){ $index = array_search($existing, $newTags); if($index !== false){ unset($newTags[$index]); } } foreach($tags as $tag){ $out[] = $tag; } foreach($newTags as $tag){ $out[] = $this->Tags->newEntity(['title'=>$tag]); } return $out; }
Auto-populating the Tag String
// src/Model/Table/ArticlesTable.php
public function initialize(array $config): void { $this->addBehavior('Timestamp'); $this->belongsToMany('Tags', [ 'joinTable' => 'articles_tags', 'dependent' => true ]); }
|