CloudinaryのAdmin APIとSearch APIを使って、Clouidanryの画像を管理する

#この記事は
[CloudinaryのUpload APIを使って、画像をアップロードする](https://qiita.com/kanaxx/items/64327acad6a5ac7f241d) の続きで、CloudinaryのAdmin APIとSearch APIを試します。

環境の準備などは、前記事を参照でお願いします。

#Admin API & Search APIとは

## Admin API
Admin APIは、Cloudinaryの設定面を管理画面の代わりに操作できるAPIです。設定周りだけでなく、動画や画像（総じてAssetアセット）の操作もできます。Admin APIは1時間あたりの呼び出し回数に制限があります。

フリープランでは、1時間あたり500回です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/94b3fa94-80a5-237c-d023-50e1a1fc1cde.png)

## Search API
Search APIは、Cloudinary内に蓄積されたアセットをLuceneのようなクエリーを書いて検索するAPIです。さまざまなファイルの属性（ファイル名だけでなくタグ、登録・更新の時間、補足の説明など）を検索対象にして探すことができます。
APIのsearchメソッドはAdmin APIの一部なので、利用回数はAdminAPIと共有されます。認証方法、ページネーション、エラーハンドリングなどはAdmin APIと同じなので、ドキュメントを見るべし。

##Admin APIで操作可能なオブジェクト
Admin APIでは、以下のものが操作可能です。（一部不明点あり）

###**Resources**
アセットの操作ができます。アセットの検索する、情報を参照する、更新する、削除するなどの操作です。
メインの機能になります。

| 機能 | 説明 | 
|:--|:--|
| Get resources  | アセットを検索  |   
| Get resources by tag  | アセットをタグで検索  |   
| Get resources by context  | アセットをコンテキストで検索 |   
| Get resources in moderation queues | 一時保留になっている画像を探す  |   
| Get the details of a single resource  | 1つのアセットの詳細を取る  |   
| Search for resources  | アセットを検索する(Search API) |   
| Update details of an existing resource  | アセットの情報を更新する  |   
| Restore resources  | バックアップからリストア  |   
| Update access mode  | access modeを更新  |   
| Delete resources  | 複数のアセットを削除  |   
| Delete resources by tags| タグを指定してアセットを削除 | 



https://cloudinary.com/documentation/admin_api#resources

###**Transformations**
変換を作ったり消したりできます。管理画面のここに表示される一覧のことです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/7f8fd4aa-55b2-1171-e168-5f6a055f973f.png)

###**Upload mappings**
URLで外部リソースを取り込むマッピングを作ったり消したり。APIでわざわざやらないような気もする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/d142d361-2cd7-0da9-c16b-b022165f441c.png)


###**Upload presets**
アップロードしたときに変換されるプリセットを作ったり消したり。これも、APIでわざわざやらないような気もする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/34b2251a-42ce-f044-14d9-fa3976a65817.png)


###**Metadata fields**
Cloudinaryで管理する全てのアセットに、共通のメタデータを定義する機能をAPIでやるのですね。
コンソールだと、設定メニューの右側にあるリンクから行ける機能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/d22b3f6d-224c-8585-6a1e-499dd8a85d67.png)

型を決めて、メタデータの箱を登録すると、各アセットにメタデータを登録できるようになると。（使うかな。。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168315/fd090e87-6538-7387-706b-fe3017b5be93.png)


###**Folders**
フォルダを作ったり消したり

###**Adaptive streaming profiles**
(。´・ω・)ん?これはなんだ？
https://cloudinary.com/documentation/admin_api#adaptive_streaming_profiles

###**Tags**
画像にタグをつけるのではなくて、システム内のタグの種類を見るだけ。

###**Usage**
システムの利用状況（プラン、StorageやBandwidth）を確認する

###Ping
いきてますかー？

詳細はこちらに。
https://cloudinary.com/documentation/admin_api

#SDKに頼らずにHTTPSで直接実行してみる
Admin APIは、`API Key`と`API Secret`だけで接続することができます。Upload APIのように、パラメータから複雑な手順で生成するsignatureは不要です。使い方さえ覚えてしまえば、PostManやVS CodeのHTTP Clientでも実行可能です。

##Admin APIの接続先
APIはAPIキーとAPIシークレットをURLに埋める形で作ります。最後のアクション部分を変えることで呼び出す処理が変わります。

Admin APIのURLのパターン
https\://**\<API_KEY>**:**\<API_SECRET>**@api.cloudinary.com/v1_1/**\<CLOUD_NAME>**/**\<ACTION>**

:white_check_mark:自分の環境のURL
https\://378xxx:eyhxxx<span>@</span>api.cloudinary.com/v1_1/kanaxx/


レスポンスヘッダーにAPIの実行回数に関する情報が含まれています。Headerから残り回数を読み取って残りが少なくなったら待機するか、エラーコードでエラー処理をするのか、どちらか実装しておきましょう。
上限が超えたときには、`420: Rate limited.`になります。

```console:HTTP_Response_Header
X-FeatureRateLimit-Limit: 500
X-FeatureRateLimit-Remaining: 498
X-FeatureRateLimit-Reset: Sat, 18 Apr 2020 10:00:00 GMT
```

APIの実行回数は、時計の針が00分になるときに、全てリセットされる仕組みです。

##コード
Admin APIの中で簡単な`/usage` Actionにリクエストを投げ込むPHPです。

```php:admin-api.php
<?php
//各自設定
$cloudName='kanaxx';
$resourceType = 'image';
$apiKey = '37826219xxx';
$apiSecret = 'eyhlvE3t16l1WYxxx';

$api = "https://{$apiKey}:{$apiSecret}@api.cloudinary.com/v1_1/{$cloudName}/";

$usage = $api . '/usage';

// 以下、API実行部分
$ch = curl_init();

// オプションを設定
curl_setopt($ch, CURLOPT_URL, $usage); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 
curl_setopt($ch, CURLOPT_VERBOSE, 1);
// curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post));

// API実行
$json = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

echo 'Response Code:' . $code . PHP_EOL;
echo $json . PHP_EOL;

$result = json_decode($json, true);
var_dump($result);

```

難しくないですね。
response code=200でないときだけ、ちゃんと対応をしましょう。


#SDKを使う
前回同様、PHPのSDKを使ってCloudinaryをコントロールしてみます。管理コンソールでやればよさそうなものをやってもしょうがないので、画像の操作系だけやってみます。

```php:admin-api-sdk.php
$api = new \Cloudinary\Api();

//１．使用量の確認
$usage = $api->usage();
//データ全体
var_export($usage);
//細かくみるなら、こんな感じで
echo 'plan = ' . $usage['plan'] . PHP_EOL;
echo 'storage =  ' . $usage['storage']['usage'] . PHP_EOL;
echo PHP_EOL;

//２．簡易な検索機能で画像を探す。
$resources = $api->resources([
    'max_results' => 10,
    'resource_type '=>'image',
    'type'=>'upload',
    'prefix'=>'upload-test/test'
]);
var_export($resources);

//各リソースは、'resource'キーに配列が入っている
foreach($resources['resources'] as $r){
    echo $r['public_id'] . PHP_EOL;
}

echo PHP_EOL;

//３．画像を1件だけ探すと情報がたくさんとれる。単数形なので注意
$resource = $api->resource('upload-test/test4_l8ro1n');
var_export($resource);
echo PHP_EOL;

//４．Cloudinary内のタグ一覧
$tags = $api->tags(['max_results'=>500]);
var_export($tags['tags']);


```

##実行結果

SDKで実行したときは、PHPのオブジェクトにラップされて戻ってきます。

resources（複数形）の戻り

```php:resources
 array (
      'public_id' => 'upload-test/test01',
      'format' => 'png',
      'version' => 1586779187,
      'resource_type' => 'image',
      'type' => 'upload',
      'created_at' => '2020-04-13T11:59:47Z',
      'bytes' => 1530,
      'width' => 150,
      'height' => 150,
      'access_mode' => 'public',
      'url' => 'http://res.cloudinary.com/kanaxx/image/upload/v1586779187/upload-test/test01.png',
      'secure_url' => 'https://res.cloudinary.com/kanaxx/image/upload/v1586779187/upload-test/test01.png',
    )
```

resource（単数）呼び出しの戻り方

```php:resource
array(
   'public_id' => 'upload-test/test4_l8ro1n',
   'format' => 'png',
   'version' => 1587268817,
   'resource_type' => 'image',
   'type' => 'upload',
   'created_at' => '2020-04-19T04:00:17Z',
   'bytes' => 1433,
   'width' => 150,
   'height' => 150,
   'backup' => true,
   'access_mode' => 'public',
   'url' => 'http://res.cloudinary.com/kanaxx/image/upload/v1587268817/upload-test/test4_l8ro1n.png',
   'secure_url' => 'https://res.cloudinary.com/kanaxx/image/upload/v1587268817/upload-test/test4_l8ro1n.png',
   'next_cursor' => '38cb428ee5bce2c43ae9db1442eecf8579d95eebc82715ce87a4686467b70d23',
   'derived' =>  array (
    0 =>
    array (
      'transformation' => 't_media_lib_thumb',
      'format' => 'png',
      'bytes' => 1139,
      'id' => 'f00301b389c96f5ea828a696ae8a3f48',
      'url' => 'http://res.cloudinary.com/kanaxx/image/upload/t_media_lib_thumb/v1587268817/upload-test/test4_l8ro1n.png',
      'secure_url' => 'https://res.cloudinary.com/kanaxx/image/upload/t_media_lib_thumb/v1587268817/upload-test/test4_l8ro1n.png',
    ),
    1 =>
    array (
      'transformation' => 'a_hflip,w_200/a_45,g_south_west,l_pop_gazouha_image_desu,w_70',
      'format' => 'png',
      'bytes' => 6549,
      'id' => '6de1174745014ab48bca093fce6b6b58',
      'url' => 'http://res.cloudinary.com/kanaxx/image/upload/a_hflip,w_200/a_45,g_south_west,l_pop_gazouha_image_desu,w_70/v1587268817/upload-test/test4_l8ro1n.png',
      'secure_url' => 'https://res.cloudinary.com/kanaxx/image/upload/a_hflip,w_200/a_45,g_south_west,l_pop_gazouha_image_desu,w_70/v1587268817/upload-test/test4_l8ro1n.png',
    ),
    2 =>
    array (
      'transformation' => 'bo_3px_solid_blue,r_max,w_120',
      'format' => 'png',
      'bytes' => 3548,
      'id' => '662d3f6e8f9d759708128812e97cb073',
      'url' => 'http://res.cloudinary.com/kanaxx/image/upload/bo_3px_solid_blue,r_max,w_120/v1587268817/upload-test/test4_l8ro1n.png',
      'secure_url' => 'https://res.cloudinary.com/kanaxx/image/upload/bo_3px_solid_blue,r_max,w_120/v1587268817/upload-test/test4_l8ro1n.png',
    ),
    3 =>
    array (
      'transformation' => 'bo_2px_solid_red,w_100',
      'format' => 'png',
      'bytes' => 1172,
      'id' => 'e66d0e2d6304f57cd008ddb141bc07cd',
      'url' => 'http://res.cloudinary.com/kanaxx/image/upload/bo_2px_solid_red,w_100/v1587268817/upload-test/test4_l8ro1n.png',
      'secure_url' => 'https://res.cloudinary.com/kanaxx/image/upload/bo_2px_solid_red,w_100/v1587268817/upload-test/test4_l8ro1n.png',
    ),
  ),
))
```

Admin APIの2つのリソース系のメソッドの大きな違いは、Get the details of a single resource(単一`public_id`を指定)は、deliveredの画像の一覧が取れることですね。deliveredのリソースとは、元画像を元にして変換して増殖した分ということです。


#Search API SDK
Search APIはAdmin APIの一部なのに、Admin APIと使い方が違います。しかも、PHP-SDKの公式マニュアルも微妙に間違えていたので注意です。

```php:example_from_official_page
//https://cloudinary.com/documentation/search_api#syntax

//↓コンパイルエラーです
result = \Cloudinary\search->execute();
```

SDKのソースを調べてみましたが、`new Search()`で一度インスタンス化すれば使えそうです。
`$search`インスタンスは内部に`expression()`や`max_results()`などのメソッドチェーンで渡した条件が保持されているので、インスタンスを使いまわすときには気を付けましょう。同一条件でページネーションしていくケース以外では、使いまわさないほうがよさそうです。

:warning: 例1と例2で使いまわすと痛い目に合います。（1件しか取れななくて1時間くらいハマりました）

##SearchAPIのサンプルコード

```php
<?php
require_once "vendor/autoload.php";
\Cloudinary::config([ 
    "cloud_name" => "kanaxx", "secure" => true,
    "api_key" => "37826219xxx", "api_secret" => "eyhlvE3t16l1WYgdxxx", 
]);


//例1. public_idを指定して検索
//public_id=aだと完全一致、public_id:aだと部分一致
$result = (new \Cloudinary\Search())
            ->max_results(10)
            ->expression('public_id=upload-test/test4_l8ro1n')
            ->execute();

var_export($result);
echo PHP_EOL;


//例2. 一番新しい画像を探す (order by created_at desc limit 1)
//Search APIだとMatch件数が取れるので、expressionを指定しないクエリーなし画像の総数が分かる
$search = new \Cloudinary\Search();
$result = $search
            ->max_results(1)
            ->sort_by('created_at','desc')
            ->execute();
var_export($result);
echo PHP_EOL;
            

//例3. フォルダ配下にあるファイルを検索
//pagenationのカーソルを使うためにmax=10で実行
$search = new \Cloudinary\Search();

//基本的な条件だけ先に組み立てておいて
$search->expression('folder=upload-test AND filename=api*')
        ->max_results(10)
        ->sort_by('created_at','desc');
        
$next = '';
$no = 0;
do{
    //カーソルの値を足してから、execute
    //この場合だけは、$searchインスタンスを使いまわしてよい。
    $result = $search->next_cursor($next)->execute();

    $resources = $result['resources']??[];
    echo count($resources) . '/' . $result['total_count'] . PHP_EOL;

    $next =  $result['next_cursor']??'';
    echo 'next cursor ==>' . $next . PHP_EOL;

    //画像の一覧
    foreach($resources as $resource){
        $no++;
        echo $no . ' ' . $resource['public_id'] .'|'. $resource['created_at'] . PHP_EOL;
    }
}while($next != '');

           
```

##実行結果

###例1
1件だけ取れるので、`total_count=1`で、`resources`配列に値が入ってきます。

```php
Cloudinary\Api\Response::__set_state(array(
   'total_count' => 1,
   'time' => 116,
   'resources' =>
  array (
    0 =>
    array (
      'public_id' => 'upload-test/test4_l8ro1n',
      'folder' => 'upload-test',
      'filename' => 'test4_l8ro1n',
      'format' => 'png',
      'version' => 1587268817,
      'resource_type' => 'image',
      'type' => 'upload',
      'created_at' => '2020-04-19T04:00:17+00:00',
      'uploaded_at' => '2020-04-19T04:00:17+00:00',
      'bytes' => 1433,
      'backup_bytes' => 1433,
      'width' => 150,
      'height' => 150,
      'aspect_ratio' => 1.0,
      'pixels' => 22500,
      'url' => 'http://res.cloudinary.com/kanaxx/image/upload/v1587268817/upload-test/test4_l8ro1n.png',
      'secure_url' => 'https://res.cloudinary.com/kanaxx/image/upload/v1587268817/upload-test/test4_l8ro1n.png',
      'status' => 'active',
      'access_mode' => 'public',
      'access_control' => NULL,
      'etag' => 'e3ae356ead1e954aa25ff8fe77e344b3',
      'created_by' =>
      array (
        'access_key' => '378262191665246',
      ),
      'uploaded_by' =>
      array (
        'access_key' => '378262191665246',
      ),
    ),
  ),
))
```

###例2の
total_count=448件のうち、一番新しい画像が1つだけ返ってきます。

```php
array(
   'total_count' => 448,
   'time' => 785,
   'next_cursor' => 'f9f3441bf70cb7a277280e548b4a824ae34db3c441d2106674a7f3955a140748ecdd7b525feaf9ce0efcfaeba7926664',
   'resources' =>
  array (
    0 =>
    array (
      'public_id' => 'upload-test/api-context3',
      'folder' => 'upload-test',
      'filename' => 'api-context3',
      'format' => 'jpg',
      'version' => 1587475767,
      'resource_type' => 'image',
      'type' => 'upload',
      'created_at' => '2020-04-21T13:29:27+00:00',
      'uploaded_at' => '2020-04-21T13:29:27+00:00',
      'bytes' => 2639,
      'backup_bytes' => 2639,
      'width' => 150,
      'height' => 150,
      'aspect_ratio' => 1.0,
      'pixels' => 22500,
      'url' => 'http://res.cloudinary.com/kanaxx/image/upload/v1587475767/upload-test/api-context3.jpg',
      'secure_url' => 'https://res.cloudinary.com/kanaxx/image/upload/v1587475767/upload-test/api-context3.jpg',
      'status' => 'active',
      'access_mode' => 'public',
      'access_control' => NULL,
      'etag' => 'ef01d44d8742ad19cd34739f4ed25b9d',
      'created_by' =>
      array (
        'access_key' => '378262191665246',
      ),
      'uploaded_by' =>
      array (
        'access_key' => '378262191665246',
      ),
    ),
  ),
))
```

###例3
全部で12件の結果を10件刻みで取得しました。next_cursorがemptyになったらbreakします。sort_by指定をしているので、新しい順に並んで取れます。

```console
件数：12
next cursor ==>1bcfc7ba149c49ba483be2ccd755ed6e393aea3b6ed3708781dd309e6c7cc211cde9171c341dd21ea30f0dba4e5e0e34
1 upload-test/api-context3|2020-04-21T13:29:27+00:00
2 upload-test/api-context2|2020-04-21T13:29:21+00:00
3 upload-test/api-context1|2020-04-21T13:29:12+00:00
4 upload-test/api-accessmode02|2020-04-20T23:53:35+00:00
5 upload-test/api-accessmode03|2020-04-20T23:51:08+00:00
6 upload-test/api-accessmode01|2020-04-20T23:40:00+00:00
7 upload-test/api-tag03|2020-04-20T16:56:33+00:00
8 upload-test/api-tag02|2020-04-20T16:44:01+00:00
9 upload-test/api-tag01|2020-04-20T16:43:24+00:00
10 upload-test/api-test03|2020-04-20T16:39:15+00:00
件数：12
next cursor ==>
11 upload-test/api-test02|2020-04-20T16:38:20+00:00
12 upload-test/api-test01|2020-04-20T16:37:47+00:00
```


#Admin APIとUpload APIの重複操作
今回、APIを調べていて`Admin API`と`Upload API`でどちらでもできるものがあったので、整理しておきます。Upload APIは回数制限がないので、どちらでもできるのであればUpload APIでやるほうがよさそうです。

| 操作 | Admin API | Upload API | memo |
|:-:|:-:|:-:|:-:|
| 画像登録  | x | o |   |
| 名前変更  | x  | o  |   |
| 画像の削除  | 複数  | 1つずつ  |   |
| 画像の検索  | o  | x | resource / search の2種類あり |
| 情報取得  | o  | x  | resource / resources で取得する情報に差があり |
| Tag操作  | 1つの画像に複数のタグを設定  | 複数の画像に1つのタグを設定  |   |
| Context操作  | 1つの画像に設定  | 複数の画像に設定  |   |
| AccessMode変更  | x | upload時に設定  | RESTではできるのにSDKに実装されていない |

#まとめ
Cloudinaryファンとして、2つのAPIの使い方を簡単に書いてみましたけど、まぁ、あまり出番がないかもですねぇ。