CakePHP で作成されたオープンソースに機能を追加する手順

今回は CakePHP で作られた
オープンソースにオリジナルの機能を追加する方法を
紹介してみようと思います。

この記事の肝は
・データベースからデータを拾う方法
・データベースにデータを追加する方法
の 2 点になります。

さくっとこの記事を読みたい方は
データベースを触る箇所まで
読み飛ばす事を推奨します。

***

機能を追加する方法の概要

機能を追加する時は
対象の View と Controller に追記します。

例えば irohaBoard で
「コース」に「複製」機能を追加したい場合は
View/Courses の admin_index.ctp と
Controller の CoursesController.php を編集します。

***

View の編集方法

View を編集する場合は
html を編集する感覚で編集します。

例えば「複製」ボタンを追加したい場合は
「削除」ボタンなどの似たボタンを複製して
action を copy に変更します。
「削除」というテキストも
「複製」に変えます。

***

Controller の編集方法

Controller を編集する場合は
自作のメソッドを追加する形になります。

追加するメソッド以外は触る必要がありません。

View ファイルを編集した際に
action を copy で作成した場合は
admin_copy メソッドか
copy メソッドを作成する事になると思います。

admin_edit メソッドや
edit メソッドなど
よく似たメソッドを丸々コピーして
メソッド名を変更して
中身を書き換える事を推奨します。

そして中にデータベースにアクセスする記述をします。
データベースへアクセスする記述が
この記事の肝になります。
それをこれから説明していきます。

***

データベースからデータを取得する手順

下記のようにデータベースを取得・変更をします。
これは Course を複製する機能の記述ですが
記事を読んでいて分からなくなった場合に
このメソッドを読みなおして下さい。

```php:改造のサンプル
	/**
	 * コースのコピー
	 * @param int $course_id コースID
	 */
	public function admin_copy($course_id = null)
	{
		/*if($this->readAuthUser('role') != 'admin') 
		{
			throw new NotFoundException(__('Invalid course'));
		}*/
		if(!$this->Course->exists($course_id))
		{
			throw new NotFoundException(__('Invalid course'));
		}

		$myGet = $this->Course->find('first', array('conditions' => array('Course.id' => $course_id)));

		$coursedata = array('title' =>$myGet['Course']['title'], 'introduction' => $myGet['Course']['introduction'], 'opened' => $myGet['Course']['opened'], 'created' => $myGet['Course']['created'], 'modified' => $myGet['Course']['modified'], 'deleted' => $myGet['Course']['deleted'], 'sort_no' => $myGet['Course']['sort_no'], 'comment' => $myGet['Course']['comment'], 'user_id' => $myGet['Course']['user_id']);
		$this->Course->save($coursedata);
		$oneTimeID = $this->Course->id;

		//$this->log($myGet['Content'], 'error');
		//$this->log($myGet, 'error');
		//$this->log($this->Course->id, 'error');

		foreach ($myGet['Content'] as $article) {
			//$this->log($article, 'error');
			$oneTimeID2 = $article['id'];
			$contentdata = array('user_id' => $article['user_id'], 'course_id' => $oneTimeID, 'title' => $article['title'], 'url' => $article['url'], 'file_name' => $article['file_name'], 'kind' => $article['kind'], 'body' => $article['body'], 'timelimit' => $article['timelimit'], 'pass_rate' => $article['pass_rate'], 'question_count' => $article['question_count'], 'wrong_mode' => $article['wrong_mode'], 'status' => $article['status'], 'opened' => $article['opened'], 'created' => $article['created'], 'modified' => $article['modified'], 'deleted' => $article['deleted'], 'sort_no' => $article['sort_no'], 'comment' => $article['comment']);
			//$this->log($contentdata, 'error');
			$this->loadModel('Content');
			$this->Content->create();
			$this->Content->save($contentdata);
			$oneTimeID3 = $this->Content->id;
			
			if($article['kind'] ==  'test' || $article['kind'] ==  'enquete') {
				$this->loadModel('ContentsQuestion');
				$myGet2 = $this->ContentsQuestion->find('all', array('conditions' => array('ContentsQuestion.content_id' => $oneTimeID2)));

				//$this->log($myGet2, 'error');

				foreach($myGet2 as $oneMyGet2) {
					//$this->log($oneMyGet2, 'error');
					$contentsQuestiondata = array('content_id' => $oneTimeID3, 'question_type' => $oneMyGet2['ContentsQuestion']['question_type'], 'title' => $oneMyGet2['ContentsQuestion']['title'], 'body' => $oneMyGet2['ContentsQuestion']['body'], 'image' => $oneMyGet2['ContentsQuestion']['image'], 'options' => $oneMyGet2['ContentsQuestion']['options'], 'correct' => $oneMyGet2['ContentsQuestion']['correct'], 'score' => $oneMyGet2['ContentsQuestion']['score'], 'explain' => $oneMyGet2['ContentsQuestion']['explain'], 'comment' => $oneMyGet2['ContentsQuestion']['comment'], 'created' => $oneMyGet2['ContentsQuestion']['created'], 'modified' => $oneMyGet2['ContentsQuestion']['modified'], 'sort_no' => $oneMyGet2['ContentsQuestion']['sort_no']);
					unset($this->ContentsQuestion->validate['question_type']);
					$this->ContentsQuestion->create();
					$this->ContentsQuestion->save($contentsQuestiondata);
				}
			}
		}
		return $this->redirect(['action' => 'index']);
	}
```

データベースからデータを取得する場合は

`$this->loadModel('Controller 名(最後の s を抜く) ');`
`$this -> Controller 名(最後の s を抜く) ->find`
メソッドを使うのが楽だと思います。

loadModel メソッドは
`$this -> Controller 名(最後の s を抜く)` を実行する際に
実行する必要があります。(使用する Controller が変わる度に実行)

1 件取得したい場合は
`$myGet = $this->Course->find('first', array('conditions' => array('Course.id' => $course_id)));`
で取得して
`$myGet['Course']['title']`
等で取得する形です。

'Course' 部分は
`$this->log($myGet, 'error');`
で調査します。

複数件取得したい場合は
`$myGet2 = $this->ContentsQuestion->find('all', array('conditions' => array('ContentsQuestion.content_id' => $oneTimeID2)));`
等の記述で取得した後に
`foreach($myGet2 as $oneMyGet2) {`

`}`
で各要素について実行していきます。

`$oneMyGet2['ContentsQuestion']['title']`
等で各値を取得します。

新規で見た目を作成するのは面倒なため
処理が完了したら元の画面に
リダイレクトする事を推奨します。

`return $this->redirect(['action' => 'index']);`
といった記述になると思われます。

***

データベースに要素を追加する

データベースに要素を追加する場合は

```php:データベースに要素を追加する
$mydata = array('title' => 'Hellow World', 'body' => 'こんにちは世界');
$this->loadModel('Controller 名(最後の s を抜く)');
$this->Controller 名(最後の s を抜く)->create();
$this->Controller 名(最後の s を抜く)->save($mydata);
//$oneTimeID = $this->Controller 名(最後の s を抜く)->id; //新規作成された id を知りたい場合
```

という記述をします。

バリデーションエラーが発生した場合は
`unset($this->ContentsQuestion->validate['question_type']);`
といった記述が必要な可能性もあります。

どのバリデーションがエラーになっているか調べる場合は
`$this->Controller 名(最後の s を抜く)->validationErrors`
を使用します。

```php:バリデーションエラーの出し方
            if (!$this->ContentsQuestion->save($contentsQuestiondata)) {
                $this->log($this->ContentsQuestion->validationErrors, 'error')
            }
```

等が手堅いと思います。

***

以上、駆け足になりましたが
CakePHP で作成されたオープンソースに
機能を追加する手順を紹介しました。

じっくり紹介してしまうと
記事を量産する事になるため
駆け足で紹介させて頂きました。

この記事を使ってオープンソースに機能を
追加出来た人が現れたならば嬉しいです。

なお私は Windows11 標準の
メモ帳で機能を追加しています。

さくっと実装する場合は
エディタをインストールする手間を嫌って
メモ帳で機能を追加するのも一考です。

閲覧ありがとうございました。