【備忘録】初めてのCakePHP⑤ - ファイル操作編

# ディレクトリ操作

```php:config/paths
define('ROOT', dirname(__DIR__)); // /var/www/cakapp
define('STORAGE_PATH', ROOT . '/src/storage');
```

```php:src/Controllers/AppController.php
use Cake\Filesystem\Folder;
use Cake\Filesystem\File;

$dir = new Folder(STORAGE_PATH);
$dir->pwd(); // /var/www/cakeapp/src/storage
// '/var/www/cakapp/src/storage'ディレクトリ直下に'test'ディレクトリを作成
$dir->create('test'); // true
// ↑で作成した'test'ディレクトリの削除
$dir->cd('test'); // ディレクトリ移動
$dir->delete(); // true
```

# ファイル操作
```php:src/Controller/AppController.php
$f = new File(STORAGE_PATH. DS . 'tmp.png); // DSはDIRECTORY_SEPARATOR
$f->delete($tmpDir . DS . $deletePath);
$f->close();
```

# 画像のアップロード - 指定のディレクトリ

#### カラム追加
```bash:/var/www/cakeapp
$ bin/cake bake migration AddFileNameToUsers file_name:string
$ bin/cake migrations migrate
```

#### コントローラ
```php:src/Controller/UsersController.php
<?php

namespace App\Controller;

use App\Controller\AppController;
use Cake\Collection\Collection;
use Cake\Event\Event;
use Cake\Filesystem\Folder;
use Cake\Filesystem\File;

class UsersController extends AppController
{

  // 中略

  public function edit()
  {
    $user = $this->Users->get($this->Auth->user('id'), [
      'contain' => [],
    ]);
    if ($this->request->is(['patch', 'post', 'put'])) {
      $data = $this->request->getData();
      $fileSource = $data['user_file']['tmp_name'];
      //  $data['file_name']に"users/[$user->id]/thumbnail_[$user->id].[拡張子]"を格納する
      $saveDir = "users/{$this->Auth->user('id')}";
      $data['file_name'] = $this->getSavePath($data['user_file']['name']);
      // アップロードだけだと２回目以降はファイル名が被ってない限り、溜まる一方なので、削除用のパスを作っておく
      $deletePath = $this->Auth->user('file_name') !== null
        && $this->getExtension($this->Auth->user('file_name')) !== $this->getExtension((string)$data['file_name'])
        ? $this->Auth->user('file_name') : '';

      $user = $this->Users->patchEntity($user, $data);

      if ($this->Users->save($user)) {
        $this->Auth->setUser($user);

        $this->putTmpFile($fileSource, $saveDir, $data['file_name']);
        if (!empty($deletePath)) {
　　　　　　$this->deleteTmpFile(STORAGE_PATH, $deletePath);        
        }
        $this->Flash->success(__('The user has been saved.'));
        return $this->redirect(['action' => 'edit']);
      }
      $this->Flash->error(__('The user could not be saved. Please, try again.'));
    }
    $this->set(compact('user'));
  }

  /**
   * @param string $originalName
   * @return string
   */
  protected function getSavePath(string $originalName): string
  {
    $fileName = "user_thumbnail_{$this->Auth->user('id')}";
    $extension = $this->getExtension($originalName);
    $fileName .= $extension;
    $saveDir = "users/{$this->Auth->user('id')}";
    return $saveDir . DS . $fileName;
  }

  protected function getExtension(string $filePath)
  {
    return mb_substr($filePath, mb_strrpos($filePath, '.'));
  }

  /**
   * @param $fileSource
   * @param $saveDir
   * @param $fileName
   */
  protected function putTmpFile($fileSource, $saveDir, $fileName)
  {
    $dir = new Folder(STORAGE_PATH);
    $dir->create($saveDir);
    $savePath = $dir->path . DS . $fileName;
    move_uploaded_file($fileSource, $savePath);
  }

  /**
   * @param string $tmpDir
   * @param string $deletePath
   */
  protected function deleteTmpFile(string $tmpDir, string $deletePath)
  {
    $f = new File($tmpDir . DS . $deletePath);
    $f->delete($tmpDir . DS . $deletePath);
    $f->close();
  }
}
```

#### ビュー

```php:src/Template/Users/edit.ctp
<?php
echo $this->Form->create($user, ['enctype' => 'multipart/form-data']);
echo $this->Form->file('user_file', [
  'accept' => 'image/jpeg,image/png,image/gif,image/svg+xml'
]);
//echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('保存'));
echo $this->Form->end();
?>
```

# 画像のアップロード - S3

アップロードと削除だけ実装する。

#### aws/aws-sdk-phpのインストール
```bash:/var/www/cakeapp
$ composer require aws/aws-sdk-php
```

#### 環境変数の設定
```env:config/.env
export AWS_S3_BUCKET="asset.example.site"
export AWS_S3_REGION="ap-northeast-1"
export AWS_S3_KEY="AKIAHOGEHOGE"
export AWS_S3_SECRET="hoGeHoge"
```

#### コンポーネントの作成
```bash:/var/www/cakeapp
$ bin/cake bake component S3Client
```

```php:src/COntroller/Component/S3ClientComponent.php
<?php

namespace App\Controller\Component;

use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use Aws\S3\S3Client;
use Aws\S3\Exception\S3Exception;

/**
 * S3Client component
 */
class S3ClientComponent extends Component
{
  /**
   * Default configuration.
   *
   * @var array
   */
  protected $_defaultConfig = [];

  protected $defaultBucket;

  public function initialize(array $config)
  {
    $this->s3 = S3Client::factory([
      'credentials' => [
        'key' => env('AWS_S3_KEY', ''),
        'secret' => env('AWS_S3_SECRET', ''),
      ],
      'region' => env('AWS_S3_REGION', ''),
      'version' => 'latest',
    ]);

    $this->defaultBucket = env('AWS_S3_BUCKET', '');
  }

  /**
   * @param string $filePath
   * @param string $storePath
   * @param string $bucketName
   * @return mixed
   */
  public function putFile(string $filePath, string $storePath, string $bucketName=null)
  {
    try {
      if(!$bucketName) $bucketName = $this->defaultBucket;
      $result = $this->s3->putObject(array(
        'Bucket'       => $bucketName,
        'Key'          => $storePath,
        'SourceFile'   => $filePath,
      ));

      return $result;
    } catch (S3Exception $e) {
      echo $e->getMessage();
    }
  }

  /**
   * @param string $filePath
   * @param string $bucketName
   * @return mixed
   */
  public function deleteFile(string $filePath, string $bucketName=null)
  {
    try {
      if(!$bucketName) $bucketName = $this->defaultBucket;
      $result = $this->s3->deleteObject(array(
        'Bucket' => $bucketName,
        'Key'    => $filePath
      ));

      return $result;
    } catch (S3Exception $e) {
      echo $e->getMessage();
    }
  }

}
```

#### コントローラ
```bash:/var/www/cakeapp
$ bin/cake bake controller S3
```

```php:src/Controller/S3Controller.php
<?php

namespace App\Controller;

use App\Controller\AppController;

/**
 * S3 Controller
 *
 *
 * @method \App\Model\Entity\S3[]|\Cake\Datasource\ResultSetInterface paginate($object = null, array $settings = [])
 */
class S3Controller extends AppController
{
  protected $storagePath;
  protected $cloudRootDir;

  public function initialize()
  {
    parent::initialize();
    $this->loadComponent('S3Client');
    $this->autoRender = false;
    $this->storagePath = STORAGE_PATH;
    $this->cloudRootDir = 'tmp/';
  }

  /**
   * @param $fileName (ex):'users/1/thumbnail_1.png'
   * @return mixed
   */
  public function upload(string $fileName)
  {
    $fileLocalPath = sprintf('%s/%s', $this->storagePath, $fileName);
    $fileStorePath = sprintf('%s%s', $this->cloudRootDir, $fileName);

    return $this->S3Client->putFile($fileLocalPath, $fileStorePath);
  }

  /**
   * @param string $deletePath = 'users/1/thumbnail_1.png'(ex)
   * @return mixed
   */
  public function delete(string $deletePath)
  {
    return $this->S3Client->deleteFile(sprintf('%s%s', $this->cloudRootDir, $deletePath));
  }
}
```

UsersControllerも修正する。

```diff:src/Controller/UsersController.php
  public function edit()
  {
    $user = $this->Users->get($this->Auth->user('id'), [
      'contain' => [],
    ]);
    if ($this->request->is(['patch', 'post', 'put'])) {
      $data = $this->request->getData();
      $fileSource = $data['user_file']['tmp_name'];
      //  $data['file_name']に"users/[$user->id]/thumbnail_[$user->id].[拡張子]"を格納する
      $saveDir = "users/{$this->Auth->user('id')}";
      $data['file_name'] = $this->getSavePath($data['user_file']['name']);
      // アップロードだけだと２回目以降はファイル名が被ってない限り、溜まる一方なので、削除用のパスを作っておく
      $deletePath = $this->Auth->user('file_name') !== null
        && $this->getExtension($this->Auth->user('file_name')) !== $this->getExtension((string)$data['file_name'])
        ? $this->Auth->user('file_name') : '';

      $user = $this->Users->patchEntity($user, $data);

      if ($this->Users->save($user)) {
        $this->Auth->setUser($user);

        $this->putTmpFile($fileSource, $saveDir, $data['file_name']);
+        $s3Controller = new S3Controller();
+        $s3Controller->upload($this->Auth->user('file_name'));
+        $this->deleteTmpFile(STORAGE_PATH, $this->Auth->user('file_name'));
        if (!empty($deletePath)) {
-　　　　　　$this->deleteTmpFile(STORAGE_PATH, $deletePath);
+　　　　　　$result = $s3Controller->delete($deletePath);
        }
        $this->Flash->success(__('The user has been saved.'));
        return $this->redirect(['action' => 'edit']);
      }
      $this->Flash->error(__('The user could not be saved. Please, try again.'));
    }
    $this->set(compact('user'));
  }
```