Laravel の Storage で disk として S3 を指定したときに deleteDirectory できない

LaravelにはStorageというファサードがあり、ローカルだろうがS3だろうが簡単ファイル操作を簡単に行うことができ、とても便利です。

例えば、localにファイルを設置したい場合は、

Storage::disk('local')->putFile('', $file);

などとすることができ、localの特定のディレクトリを丸々削除したい場合は、

Storage::disk('local')->deleteDirectory('target_dir');

と書けます。

上記はlocalでしたが、S3を対象にしたい場合には、

Storage::disk('s3')->putFile('', $file);
Storage::disk('s3')->deleteDirectory('target_dir');

locals3 にするだけで良いです。

もちろん、S3上の操作なので、S3側で権限の設定は必要です。

putFile のために s3:PutObject の権限が必要だということは感覚的にわかりますが、 deleteDirectory のためにはどんな権限が必要なのでしょう?
deleteに関することなので s3:DeleteObject のような気がするのですが、これで実行すると、以下のようなエラーが発生します。

Error executing "ListObjects" on "https://〜〜"; AWS HTTP error: Client error: `GET https://〜` resulted in a `403 Forbidden` response: <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>〜 (truncated...) AccessDenied (client): Access Denied - <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>〜</RequestId><HostId>〜</HostId></Error>

正解は、対象バケットに対して s3:ListBucket の権限が必要です。

deleteDirectory の内部では、S3ClientInterface::deleteMatchingObjectsAsync() が呼ばれており、そこで ListObjects らしきことが行われています。
S3には s3:ListObjects といった権限がなく、結局どうすれば良いかわからなかったのですが、PHPもLaravelも関係ない、以下の記事に助けられました。
dev.classmethod.jp

まとめ。
Laravel の Storage(S3) で deleteDirectory する場合には、 S3 の対象バケットs3:ListBucket の権限が必要。

Laravel の Storage で S3 の特定のディレクトリをプレフィックスとして固定したい

LaravelにはStorage というファサードがあり、ローカルだろうがS3だろうが簡単ファイル操作を簡単に行うことができ、とても便利です。

config/filesystems.phpのdisksにはデフォルトで以下のような設定があります。

'disks' => [

    'local' => [
        'driver' => 'local',
        'root' => storage_path('app'),
    ],

    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL') . '/storage',
        'visibility' => 'public',
    ],

    's3' => [
        'driver' => 's3',
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION'),
        'bucket' => env('AWS_BUCKET'),
        'url' => env('AWS_URL'),
    ],

],

.envAWSの設定をして、アクセス制限がしっかりしていれば、

Storage::disk('s3')->putFile('photos', new File('/path/to/photo'));

とするだけで、 S3上のAWS_BUCKETバケットphotosディレクトリに/path/to/photoのファイルがアップロードされます。

簡単で便利ですが、そのプロジェクトで使うディレクトリがphotos限定であったとき、毎回指定するのが面倒です。
publicの場合はrootというオプションで設定できるようですが、s3の場合にどうすれば良いのかわかりませんでした。

調べても情報はあんまり無かったのですが、山勘でやった結果うまく設定できました。 publicと同じようにrootを設定するだけです。

    'photos_s3' => [
        'driver' => 's3',
        'key' => env('AWS_ACCESS_KEY_ID'),
        'secret' => env('AWS_SECRET_ACCESS_KEY'),
        'region' => env('AWS_DEFAULT_REGION'),
        'bucket' => env('AWS_BUCKET'),
        'root' => 'photos',
        'url' => env('AWS_URL'),
    ],

これで、

Storage::disk('photos_s3')->putFile('', new File('/path/to/photo'));

とするだけで、先程と同じことが実現できます。

AtCoderで競技プログラミングというものに触れて半年が経った

はじめに

AtCoder競技プログラミングを始めてから半年が経ったので久しぶりに中間報告としてアウトプットします。

開始3ヶ月時点でのブログはこちらです。 ohshige.hatenablog.com

開始1ヶ月時点でのブログはこちらです。 ohshige.hatenablog.com

現状

現時点(2019/08/12)での数字は以下の通りです。

項目 結果 3ヶ月時点との差分
順位 6223rd ↑87
Rating 1076 (暫定) ↑93
コンテスト参加回数 13 +5
Accepted 367 +116
Rated Point Sum 54600 +4800
Longest Streak 117 days +26
Current Streak 65 days -

f:id:ohshige:20190812002412p:plain:w700 f:id:ohshige:20190812002422p:plain:w700 f:id:ohshige:20190812104525p:plain:w700

しばらくかなり忙しくしていたため、AC数もコンテスト参加数もあまり増えていません。
参加したコンテストも成績が芳しくなく、レートの伸びも悪いです。
が、着実に少しずつレートは高くなっているので、とりあえず水色を目指していきます。

途中、Streakが途切れてしまったことがかなり悲しかったです。
どれだけ忙しくても絶対に1問は解いていたのに、ある日だけすっぽりと抜けてしまっていました。
忙しくてできなかったわけではなく、ポカで忘れてしまっていたことが何よりも残念です。
(そこで投げ出すことなくゼロから始められて自分偉いなと思っています)

明らかにACペースは落ちていますが、ABC-Aに関しては 129 / 137 まで来ていて、もうあと少しです。
引き続き頑張ります。

続けてみて

毎日本気で勉強しているわけではなく、解ける問題を解いているだけで、解説を見てもわからないような問題は基本的に放置しています。
絶対に良くない続け方ではありますが、続けられるだけマシかなと思います。
こういうとき、大学時代から始めていて周りに同じように勉強している友人がいればわからない問題や解説があったときに誰かに聞けたかもしれないですが、今ではそれができないので残念です。

わからない解説は放置してると言いつつも、理解できる解説はあるので少しづつ着実に成長はしています。
最近だと優先度付きキューを覚えました。
DPはまだ全然応用できないです。
こんな感じで少しずつつ覚えていければと思います。

前回のブログで、知人が2人始めてくれたと述べましたが、おそらくその2人はもうやっていないでしょう。
残念です。

おわりに

引き続きゆっくりと進めていきたいと思います。

半年以上の継続という目標は達成できたので、ABCの過去問を全て解く、水色を目指す、コンテストへの15回以上の参加という残りの3つの目標を達成できるように頑張ります。

PhpStorm で Laravel の Blade 内でもエンティティ等の補完をしてほしい

PhpStormにおけるLaravelの補完といえば laravel-ide-helper ですが、Blade内でエンティティ等の補完をする方法がわからなかったので調べました。

結論としては、これです。 blog.jetbrains.com

例えば、以下のようなエンティティがあったとします。

<?php

declare(strict_types=1);

namespace App\Entity;

final class User
{
    private $name;

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

    public function getName(): string
    {
        return $this->name;
    }
}

そして、ControllerでUserエンティティの配列を渡してBladeを呼び出したとします。

Bladeは以下の通りです。

@extends('layouts.app')

@section('content')
  <h1>ユーザ一覧</h1>
  <ul>
  @foreach($users as $user)
    <li>{{ $user->getName() }}</li>
  @endforeach
  </ul>
@endsection

この場合、PhpStormが$usersUserエンティティの配列であることを認識していないので、{{ $user->getName() }}で補完が効きません。

そこで、Bladeを以下の通りに修正します。

@extends('layouts.app')

@php
/** @var App\Entity\User[] $users */
@endphp

@section('content')
  <h1>ユーザ一覧</h1>
  <ul>
  @foreach($users as $user)
    <li>{{ $user->getName() }}</li>
  @endforeach
  </ul>
@endsection

@phpディレクティブを使って(<?php ~ ?>でも良いですが)、そこにPHPDocコメントを書くだけです。

微妙にダサい気もしますが、補完が効かずにtypoやアクセスできないメソッドを呼んでしまうよりは良いかと思います。

PHPでは未定義でも配列の代入ができる

PHPでは、変数をあらかじめ宣言していなくてもエラー無しでいきなり配列の代入ができるということを、恥ずかしながら初めて知りました。

$hoge[] = "hoge";
var_dump($hoge);
array(1) {
  [0]=>
  string(4) "hoge"
}

同様に、こんなこともできます。

$hoge["hoge"][] = "hoge";
var_dump($hoge);
array(1) {
  ["hoge"]=>
  array(1) {
    [0]=>
    string(4) "hoge"
  }
}

公式にもしっかりと記述されていました。

角括弧構文で作成/修正
$arrがまだ存在しない場合は、新しく作成します。 つまり、これは配列を作成する方法のひとつでもあります。

そりゃそうかという感じですが、メモとして残しておきます。

PHPでGoogle Play Developer APIを使ってAndroidのレシートを検証した後にAcknowledgeしたい(超簡易メモ版)

調査してもほとんど情報を見つけることができない「Acknowledge」の挙動について超簡易的にまとめてみます。

以降、間違った情報もある可能性が高く、課金処理は慎重に実装されるべきなので、鵜呑みにはしないようお願いします。

基本的にはこちらの続きで、タイトルもほぼ一緒ですが、今回は Acknowledge についてです。 ohshige.hatenablog.com

Acknowledgeとは?

Acknowledgeとは公式によると以下です。 developer.android.com

端的に言うと、ユーザの購入行動の「確定処理」をアプリ側なりサーバ側なりでやれということでしょうか。
これをしないと払い戻しになってしまうため大変です。

そういうわけなので、言われた通りにAcknowledgeすればいいわけですが、いくつかの挙動の違いに悩んだのでまとめます。

Google APIs Client Library for PHP

google-api-php-clientでAcknowledgeを実装するには以下のように行います。
(ライブラリのインストールや認証の事前準備は前の記事の通りです)

// 定期購読の場合
$postBody = new \Google_Service_AndroidPublisher_SubscriptionPurchasesAcknowledgeRequest();
$result = $publisher->purchases_subscriptions->acknowledge([packageName], [productId], [purchaseToken], $postBody);
// 都度課金の場合
$postBody = new \Google_Service_AndroidPublisher_ProductPurchasesAcknowledgeRequest();
$result = $publisher->purchases_products->acknowledge([packageName], [productId], [purchaseToken], $postBody);

必要なパラメータがあれば公式のリファレンスを参照してください。
Purchases.subscriptions: acknowledge  |  Google Play Developer API  |  Google Developers
Purchases.products: acknowledge  |  Google Play Developer API  |  Google Developers

挙動まとめ

全体的に挙動の把握に苦労したのでまとめます。

都度課金定期購読
新バージョンのレシートそのまま204 No Content204 No Content
払い戻し済み500 Internal Server Error ( null )400 Bad Request (subscriptionNotOwnedByUser)
継続利用停止済み-400 Bad Request (subscriptionNotOwnedByUser)
旧バージョンのレシート204 No Content400 Bad Request (subscriptionNotOwnedByUser)

Acknowledgeに成功すれば204が返ってきます。

成功しなかった場合がよくわかりません。
旧バージョンのレシートに対して実行してしまった場合に都度課金と定期購読とで挙動が異なる上に、払い戻し済みのレシートに対して実行してしまった場合でも都度課金と定期購読で挙動が違って都度課金に至っては500が返ってきてしまいます。

これらに関しては今後変更になる可能性もあるため注意が必要がかもしれません。

ブログをはじめて半年が経った

このブログをはじめてから半年が経ちました。
毎週1記事はアップするようにして、よく続いているなと自分でも思っています。

もともとは2019年になったタイミングで何かやってみようと思い立ったのがキッカケでした。
ブログの目標はお小遣い稼ぎでも有名になりたいわけでもなく、ただの自己満のようなものなので、中には「小学生の感想文」のような内容と文章で公開したものもありますが、自分の紹介したいもの・勉強したこと・作ったものを書き続けられて良かったと思っています。

実は、前にもブログはやっていて、学生時代から社会人になってしばらく経つまで続けていました。
そのブログをやめてからしばらく経ち、学生のときのことも書かれていて恥ずかしいので、新しくブログを始めるなら心機一転ゼロから始めようと思いました。
その古いブログは数年間一切更新していませんが、今でも毎日のように一定数のアクセスはあるようで、このブログよりもPVは高いです。
とりあえずの目標としては、このブログを続けつつ、前の古いブログよりもアクセスを伸ばすことにしようと思います。

最後に、良かったこと大変なことを箇条書きで。

良かったこと

常日頃、ネタを探すようになりました。
何か作れるものはないか、初めて扱う技術はないか、面白い本はないか、日常にアンテナを張るようにはなりました。

間違いが無いようにしっかりと調べるようになりました。
これまでは軽く浅く調べて終わりということも多かったですが、アウトプットのためにもう一度間違いがないかしっかりと調べる癖がつきました。

休日の生活がまともになりました。
基本的に予定の無い休日に書くようにしているので、休日のダラダラと寝るだけの生活が是正され、カフェに出かけてブログを書く生活になりました。

大変なこと

ネタが無い...