cakephp4.xに慣れよう2

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
        ]);
    }