Rails を CakePHP4 に置き換えた時のメモ

## 目的

CakePHP4の勉強がてら、Railsのプロジェクトを置き換える作業を実施した。

## 環境
Ruby On Rails 3.2.11
CakePHP 4.3.5

## 基本文法の比較

#### 識別子

```ruby:Ruby
local_var     # ローカル変数
@instans_var  # インスタンス変数
@@class_var   # クラス変数
$global_var   # グローバル変数
```
```php:PHP
class Sample {
	// インスタンス変数
	static $instans_var;
	// クラス変数
	$class_var;
	public $public_class_var;
	private $private_class_var;

	function classMethod() {
		global $global_var;
		echo $global_var
		self::$instans_var = 'instans';
		$this->class_var = 'class';
		$local_var = 'local';
	}
}
$global_var = 'global';

echo Sample::$instans_var;

$sample = new Sample;
echo $sample->class_var; // クラス変数

```
#### 文字列操作

```ruby:Ruby
# 結合
str = str1 + str2
str += str3
# 部分取得
str = str[0,3]
# 分割
str = str.split(',')
``` 
```php:PHP
# 結合
$str = $str1 . $str2;
$str .= $str3;
# 部分取得
$str = substr($str,0,3);
# 分割
$str = explode(',', $str);
``` 

#### 配列

```ruby:Ruby
# Array
ary = [1]
ary << 2       # [1,2]
ary.push 3, 4  # [1,2,3,4]
puts ary[0]    # 1

# Hash
h = {a:1, b:2}
h[:c] = 3      # {a:1, b:2, c:3}
``` 
```php:PHP
# Array
$ary = [1];
$ary[] = 2;                // [1,2]
array_push($ary, 3, 4);    // [1,2,3,4]
puts $ary[0]l              // 1

# 連想配列
$h = ['a'=>1, 'b'=>2];
$h['c'] = 3;               // ['a'=>1, 'b'=>2, 'c'=>3]
``` 


#### if文

```ruby:Ruby
if str.nil?
    # 処理1
elsif str.blank?
    # 処理2
else
    # 処理3
end
``` 
```php:PHP
if (is_null($str)) {
    # 処理1
} else if ($str=="") {
    # 処理2
} else {
    # 処理3
}
``` 


#### for

```ruby:Ruby
for i in 0..100 do
    # 処理
end
for row in data do
    # 処理
end
``` 
```php:PHP
for ($i=0;$i<=100;$i++) {
    # 処理
}
foreach ($data as $row) {
    # 処理
}
```

#### each

```ruby:Ruby
array.each do |val|
     # 処理
end
hash.each do |key, val|
     # 処理
end
hash.each_with_index do |val, i|
     # 処理
end
``` 
```php:PHP
foreach ($hash as $val) {
    # 処理
}
foreach ($hash as $key => $val) {
    # 処理
}
foreach ($hash as $i => $val) {
    # 処理
}
```

#### 関数定義

```ruby:Ruby
def hello args
    # 処理
    ret # 戻り値
end
```
```php:PHP
function hello($args) {
    # 処理
    return $ret; // 戻り値
}
```

#### クラス定義
```ruby:Ruby
class ApplicationController < ActionController::Base
    NUMBER_1 = '1'

    def initialize(name)
       @name = name
    end

    def classmethod
      # 処理
    end

end   
```
```php:PHP
class ApplicationController extends AppController
{
    const NUMBER_1 = '1';
    private $name;

    public function __construct($name){
      $this->name = $name;
    }

    public function classmethod(){
    }
}
```

## フレームワークの比較

#### css, js の記述

```erb:Ruby
<%= stylesheet_link_tag "base.css" %>
<%= stylesheet_link_tag "jquery.css" %>

<%= javascript_include_tag "base.js" %>
<%= javascript_include_tag "jquery.js" %>
```
```php:PHP
<?= $this->Html->css(['base', 'jquery']) ?>
<?= $this->fetch('css') ?>

<?= $this->Html->script(['base', 'jquery']) ?>
<?= $this->fetch('script') ?>
```

#### コントローラ共通モジュール（ヘルパー）

```ruby:/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include CommonModule # /lib/common_module.rb
end
```


```php:/src/Controller/ApplicationController.php
class ApplicationController < AppController
{
    public function initialize(): void
    {
        $this->loadComponent('CommonModule');
        $this->CommonModule->classMethod();
    }
}
```
```php:/src/Controller/Component/CommonModuleComponent.php
namespace App\Controller\Component;
use Cake\Controller\Component;

class CommonModuleComponent extends Component {
	public function classMethod() {
	}
}
```

 ビューでコントローラの共通モジュール（Component）を使う

```php:/src/View/Helper/CommonModuleHelper.php
namespace App\View\Helper;
use Cake\View\Helper;
use App\Controller\Component\CommonModuleComponent;
use Cake\Controller\ComponentRegistry;

class CommonModuleHelper extends Helper
{
	function __call($methodName, $args) {
		$common = new CommonModuleComponent(new ComponentRegistry());
		return call_user_func_array(array($common, $methodName), $args);
	}
}
```


#### 共通処理（before_filter）


```ruby:/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_filter :init, only=>[:hoge1, :hoge2]
  # 以下略
end
```
```php:/src/Controller/ApplicationController.php
use Cake\Event\EventInterface;
class ApplicationController extends AppController {

public function beforeFilter(EventInterface $event)
{
	public function beforeFilter(EventInterface $event)
	{
		parent::beforeFilter($event);
		$action = $this->getRequest()->getParam('action');
		if (!in_array($action, ['hoge1', 'hoge2'])) return;
		$this->init();
	}
}
```

#### セッション

```ruby:Ruby
# 代入・更新
session[:id] = 'hoge'
# 参照
@hoge = session[:id]
# 削除
session[:id].clear
session[:id] = nil
session.delete(:id)
# 全削除
reset_session
```
```php:PHP
# セッションの取得
$session = $this->getRequest()->getSession();
# 代入・更新
$session->write('id', 'hoge');
# 参照
$hoge = $session->read('id');
# 存在し null ではない
$session->check('id');
# セッションの破棄
$session->destroy();
```

#### バイナリデータ出力（send_data）

```ruby:Ruby
send_data(data, :type=>'application/octet-stream', :filename=>'ファイル名', :disposition=>'attachment')
```

```php:PHP
	public function send_data($data, $options=[]) {
		$this->autoRender = false;
		$filename = $options['filename'];
        header("Content-type: ".$options['type']);	
		header('Content-Disposition: '.$options['disposition'].'; filename="'.$filename.'"');
		echo stream_get_contents($data);
		exit;
	}
```



#### Model

Railsと同名関数を使える共通モデルを作成

```php:PHP
declare(strict_types=1);

namespace App\Model\Table;

use Cake\ORM\Table;

/**
 * Common Model
 */
class CommonTable extends Table
{
	public $connection;
    public function initialize(array $config): void
    {
        parent::initialize($config);
		$this->connection = \Cake\Datasource\ConnectionManager::get('default');
    }

	public function insert($params) {
		return $this->query()->insert(array_keys($params))->values($params)->execute();
	}

	# .delete_all(:conditions=>[])
	public function delete_all($conditions=[]) {
		return $this->query()->delete()->where($conditions)->execute();
	}

	# .update_all(params, :conditions=>[])
	public function update_all($params, $conditions=[]) {
		return $this->query()->update()->set($params)->where($conditions)->execute();
	}

	# .find_by_sql(sql)
	public function find_by_sql($sql) {
		return $this->connection->execute($sql)->fetchAll('assoc');
	}
	
	# .find('all', :condition=>[], :order->[], :select=>[])
	public function find_all($conditions=[], $order=[], $select=[]) {
		$query = $this->find('all', ['conditions'=>$conditions, 'order'=>$order]);
		if (!empty($select)) $query = $query->select($select);
		return $query->toList();
	}

	# .find('first', :condition=>[], :order->[])
	public function find_first($conditions=[], $order=[]) {
		return $this->find('all', ['conditions'=>$conditions, 'order'=>$order])->first();
	}
	# .exists(:conditions=>[])
	public function exists($conditions) : bool{
		return parent::exists($conditions);
	}
}

```

#### ビューヘルパー

Railsと同名関数を使える共通ヘルパーを作成

```php:/src/View/Helper/RailsFormHelper.php
namespace App\View\Helper;

use Cake\View\Helper\FormHelper;
use Cake\View\StringTemplateTrait;

class RailsFormHelper extends FormHelper
{
    use StringTemplateTrait;

    protected $_defaultConfig = [
        'templates' => [
			'input' => '<input type="{{type}}" name="{{name}}" id="{{name}}"{{attrs}}/>',
		]
	];

	function hidden_field_tag($fieldName, $value) {
		return $this->hidden($fieldName, ['id'=>$fieldName, 'value'=>$value]);
	}
}
```

```php:/src/View/Helper/RailsHtmlHelper.php
namespace App\View\Helper;

use Cake\View\Helper\HtmlHelper;
use Cake\View\StringTemplateTrait;

class RailsHtmlHelper extends HtmlHelper
{
    protected $_defaultConfig = [
        'templates' => [
            'image' => '<img src="{{url}}"{{attrs}}/>',
		]
	];

	// <?= image_tag("status1.png", :title => "保留する", :style => "cursor:pointer;", :onclick => "status1_click();")
	function image_tag($path, $options = []) {
		return $this->image($path, $options);
	}
}
```

```php:/src/View/Helper/RailsUrlHelper.php
namespace App\View\Helper;

use Cake\View\Helper\UrlHelper;

class RailsUrlHelper extends UrlHelper
{

	function url_for($options = []) {
		$path = $options['controller'].'/'.$options['action'];
		$params = [];
		foreach ($options as $key => $val) {
			if (in_array($key, ['controller', 'action', 'anchor'])) {
				continue;
			}
			$params[] = $key.'='.$val;
		}
		if (!empty($params)) {
			$path .= '?'.implode('&', $params);
		}
		if (isset($params['anchor'])) {
			$path .= '#'.$params['anchor'];
		}

		return $this->build($path);
	}
}
```

#### コントローラからビューで使用する変数の設定


```ruby:/app/controllers/test_controller.rb
class TestController < ActionController::Base
  def index
    @hoge = 'aaa'
  end 
end
```
```erb:/app/views/layouts/test/index.html.erb
<%= @hoge %>
```


```php:/src/Controller/TestController.php
class TestController < AppController
{
    public function index()
    {
        $this->set('hoge', 'aaa');
    }
}
```

```php:/templates/Test/index.php
<?= $hoge ?>
```

#### I18n

```ruby:config/locales/ja.yml
ja:
  fruits:
    apple: "りんご"
    orange: "みかん"
```

```erb
<%= t("fruits.apple") %>
<%= t("fruits.orange") %>
```

CakePHPでyamlを使用できるようにする

```php:/src/I18n/Parser/YamlFileParser.php
namespace App\I18n\Parser;

class YamlFileParser
{
    public function parse($file)
    {
        return yaml_parse_file($file);
    }
}
```

Railsと同様にインデントで階層を記述してみたところ、
うまく取得できなかったので、以下のように修正。
（原因については未調査）

```php:/resources/locales/ja_JP/default.yaml
fruits.apple: "りんご"
fruits.orange: "みかん"
```
```php:
<?= __("fruits.apple") ?>
<?= __("fruits.orange") ?>
```


#### authenticity_token

https://qiita.com/yulianess23/items/038fd4dbbf7eded6d2b0

#### パスワードのエンコード・デコード

https://qiita.com/yulianess23/items/6b74d70b438356e8caf7


## 備考

実際に置き換えた内容についてのみを記述しています。
不足分や必要があれば追記します。