├── src ├── Resource.php ├── Data │ ├── VariantData.php │ ├── UploadUrlData.php │ ├── ImagesData.php │ ├── VariantsData.php │ └── ImageData.php ├── Requests │ ├── DeleteImage.php │ ├── GetImages.php │ ├── GetVariants.php │ ├── GetImage.php │ ├── PostImage.php │ ├── PostUploadUrl.php │ └── PatchImage.php ├── VariantResource.php ├── CloudflareImages.php └── ImageResource.php ├── LICENSE.md └── composer.json /src/Resource.php: -------------------------------------------------------------------------------- 1 | id); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Requests/GetImages.php: -------------------------------------------------------------------------------- 1 | connector->send($request); 15 | 16 | $data = $response->dtoOrFail(); 17 | if (! $data instanceof VariantsData) { 18 | throw new Exception('Unexpected data type'); 19 | } 20 | 21 | return $data; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Data/UploadUrlData.php: -------------------------------------------------------------------------------- 1 | json('result'); 18 | if (! is_array($data)) { 19 | throw new \Exception('Invalid response'); 20 | } 21 | 22 | return new self( 23 | id: $data['id'], 24 | uploadUrl: $data['uploadURL'], 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Requests/GetImage.php: -------------------------------------------------------------------------------- 1 | id); 22 | } 23 | 24 | public function createDtoFromResponse(Response $response): ImageData 25 | { 26 | return ImageData::fromResponse($response); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Requests/PostImage.php: -------------------------------------------------------------------------------- 1 | id); 26 | } 27 | 28 | public function createDtoFromResponse(Response $response): ImageData 29 | { 30 | return ImageData::fromResponse($response); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Ben Bjurstrom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Data/ImagesData.php: -------------------------------------------------------------------------------- 1 | $images 11 | */ 12 | public function __construct( 13 | public array $images, 14 | ) { 15 | } 16 | 17 | public static function fromResponse(Response $response): self 18 | { 19 | $data = $response->json('result'); 20 | if (! is_array($data)) { 21 | throw new \Exception('Invalid response'); 22 | } 23 | 24 | $imgArr = []; 25 | foreach ($data['images'] as $image) { 26 | $imgArr[] = new ImageData( 27 | id: $image['id'], 28 | filename: $image['filename'], 29 | uploaded: $image['uploaded'], 30 | requireSignedURLs: $image['requireSignedURLs'], 31 | variants: $image['variants'], 32 | metadata: $image['meta'] ?? [], 33 | isDraft: $image['draft'] ?? false, 34 | ); 35 | } 36 | 37 | return new ImagesData( 38 | images: $imgArr, 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Data/VariantsData.php: -------------------------------------------------------------------------------- 1 | $variants 11 | */ 12 | public function __construct( 13 | public array $variants, 14 | ) { 15 | } 16 | 17 | public static function fromResponse(Response $response): self 18 | { 19 | $data = $response->json('result'); 20 | if (! is_array($data)) { 21 | throw new \Exception('Invalid response'); 22 | } 23 | 24 | $arr = []; 25 | foreach ($data['variants'] as $variant) { 26 | $arr[] = new VariantData( 27 | id: $variant['id'], 28 | fit: $variant['options']['fit'], 29 | width: $variant['options']['width'], 30 | height: $variant['options']['height'], 31 | metadata: $variant['options']['metadata'], 32 | neverRequireSignedURLs: $variant['neverRequireSignedURLs'], 33 | ); 34 | } 35 | 36 | return new VariantsData( 37 | variants: $arr, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Data/ImageData.php: -------------------------------------------------------------------------------- 1 | $variants 12 | * @param array $metadata 13 | */ 14 | public function __construct( 15 | public string $id, 16 | public ?string $filename, 17 | public string $uploaded, 18 | public bool $requireSignedURLs, 19 | public array $variants, 20 | public array $metadata, 21 | public bool $isDraft, 22 | ) { 23 | } 24 | 25 | public static function fromResponse(Response $response): self 26 | { 27 | $data = $response->json('result'); 28 | if (! is_array($data)) { 29 | throw new Exception('Invalid response'); 30 | } 31 | 32 | return new self( 33 | id: $data['id'], 34 | filename: $data['filename'], 35 | uploaded: $data['uploaded'], 36 | requireSignedURLs: $data['requireSignedURLs'], 37 | variants: $data['variants'], 38 | metadata: $data['meta'] ?? [], 39 | isDraft: $data['draft'] ?? false, 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/CloudflareImages.php: -------------------------------------------------------------------------------- 1 | withTokenAuth($this->apiToken); 16 | } 17 | 18 | public function resolveBaseUrl(): string 19 | { 20 | return sprintf( 21 | 'https://api.cloudflare.com/client/v4/accounts/%s/images', 22 | $this->accountId 23 | ); 24 | } 25 | 26 | public function images(): ImageResource 27 | { 28 | return new ImageResource($this); 29 | } 30 | 31 | public function variants(): VariantResource 32 | { 33 | return new VariantResource($this); 34 | } 35 | 36 | public function signUrl(string $url): string 37 | { 38 | if ($this->signingKey === null) { 39 | throw new Exception('Signing key is not set'); 40 | } 41 | 42 | $urlPath = parse_url($url, PHP_URL_PATH); 43 | if (! $urlPath) { 44 | throw new Exception('Unable to parse URL'); 45 | } 46 | 47 | $sig = hash_hmac('sha256', $urlPath, $this->signingKey); 48 | 49 | return $url.'?sig='.$sig; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benbjurstrom/cloudflare-images-php", 3 | "description": "A PHP client for the Cloudflare Images API", 4 | "keywords": ["cloudflare images", "cloudflare", "php", "package"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Ben Bjurstrom", 9 | "email": "bbjurstrom@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.1.0", 14 | "saloonphp/saloon": "^2.0" 15 | }, 16 | "require-dev": { 17 | "laravel/pint": "^1.4", 18 | "pestphp/pest": "^2.0.0", 19 | "pestphp/pest-plugin-arch": "2.x-dev", 20 | "phpstan/phpstan": "^1.9.11", 21 | "symfony/var-dumper": "^6.2.3" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "BenBjurstrom\\CloudflareImages\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "Tests\\": "tests/" 31 | } 32 | }, 33 | "minimum-stability": "dev", 34 | "prefer-stable": true, 35 | "config": { 36 | "sort-packages": true, 37 | "preferred-install": "dist", 38 | "allow-plugins": { 39 | "pestphp/pest-plugin": true 40 | } 41 | }, 42 | "scripts": { 43 | "lint": "pint -v", 44 | "test:lint": "pint --test -v", 45 | "test:types": "phpstan analyse --ansi", 46 | "test:unit": "pest --colors=always", 47 | "test": [ 48 | "@test:lint", 49 | "@test:types", 50 | "@test:unit" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ImageResource.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected ?array $metadata = null; 22 | 23 | protected ?bool $private = null; 24 | 25 | protected ?string $customId = null; 26 | 27 | public function private(?bool $private = true): self 28 | { 29 | $this->private = $private; 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * @param array $metadata 36 | */ 37 | public function withMetadata(array $metadata): self 38 | { 39 | $this->metadata = $metadata; 40 | 41 | return $this; 42 | } 43 | 44 | public function withCustomId(string $id): self 45 | { 46 | $this->customId = $id; 47 | 48 | return $this; 49 | } 50 | 51 | public function list(?int $perPage = null, ?int $page = null): ImagesData 52 | { 53 | $request = new GetImages(); 54 | 55 | if ($page) { 56 | $request->query()->add('page', $page); 57 | } 58 | if ($perPage) { 59 | $request->query()->add('per_page', $perPage); 60 | } 61 | 62 | $response = $this->connector->send($request); 63 | $data = $response->dtoOrFail(); 64 | if (! $data instanceof ImagesData) { 65 | throw new Exception('Unexpected data type'); 66 | } 67 | 68 | return $data; 69 | } 70 | 71 | public function get(string $id): ImageData 72 | { 73 | $request = new GetImage($id); 74 | $response = $this->connector->send($request); 75 | 76 | $data = $response->dtoOrFail(); 77 | if (! $data instanceof ImageData) { 78 | throw new Exception('Unexpected data type'); 79 | } 80 | 81 | return $data; 82 | } 83 | 84 | public function delete(string $id): bool 85 | { 86 | $request = new DeleteImage($id); 87 | $response = $this->connector->send($request); 88 | 89 | return $response->successful(); 90 | } 91 | 92 | public function update(string $id): ImageData 93 | { 94 | $request = new PatchImage($id); 95 | if ($this->metadata) { 96 | $request->body()->add('metadata', $this->metadata); 97 | } 98 | 99 | if (! is_null($this->private)) { 100 | $request->body()->add('requireSignedURLs', $this->private); 101 | } 102 | 103 | $response = $this->connector->send($request); 104 | 105 | $data = $response->dtoOrFail(); 106 | if (! $data instanceof ImageData) { 107 | throw new Exception('Unexpected data type'); 108 | } 109 | 110 | return $data; 111 | } 112 | 113 | public function create(string $image, string $fileName): ImageData 114 | { 115 | $request = new PostImage(); 116 | $request->body()->add( 117 | name: 'file', 118 | contents: $image, 119 | filename: $fileName 120 | ); 121 | $request = $this->addMetadata($request); 122 | $request = $this->addPrivacy($request); 123 | $request = $this->addCustomId($request); 124 | $response = $this->connector->send($request); 125 | 126 | $data = $response->dtoOrFail(); 127 | if (! $data instanceof ImageData) { 128 | throw new Exception('Unexpected data type'); 129 | } 130 | 131 | return $data; 132 | } 133 | 134 | public function createFromUrl(string $url): ImageData 135 | { 136 | $request = new PostImage(); 137 | $request->body()->add( 138 | name: 'url', 139 | contents: $url, 140 | ); 141 | 142 | $request = $this->addMetadata($request); 143 | $request = $this->addPrivacy($request); 144 | $request = $this->addCustomId($request); 145 | $response = $this->connector->send($request); 146 | 147 | $data = $response->dtoOrFail(); 148 | if (! $data instanceof ImageData) { 149 | throw new Exception('Unexpected data type'); 150 | } 151 | 152 | return $data; 153 | } 154 | 155 | public function getUploadUrl(): UploadUrlData 156 | { 157 | $request = new PostUploadUrl(); 158 | $request = $this->addMetadata($request); 159 | $request = $this->addPrivacy($request); 160 | $request = $this->addCustomId($request); 161 | 162 | $response = $this->connector->send($request); 163 | 164 | $data = $response->dtoOrFail(); 165 | if (! $data instanceof UploadUrlData) { 166 | throw new Exception('Unexpected data type'); 167 | } 168 | 169 | return $data; 170 | } 171 | 172 | protected function addPrivacy(PostUploadUrl|PostImage $request): PostUploadUrl|PostImage 173 | { 174 | if (! is_null($this->private)) { 175 | $request->body()->add( 176 | name: 'requireSignedURLs', 177 | contents: $this->private ? 'true' : 'false', 178 | ); 179 | } 180 | 181 | return $request; 182 | } 183 | 184 | protected function addMetadata(PostUploadUrl|PostImage $request): PostUploadUrl|PostImage 185 | { 186 | if ($this->metadata) { 187 | $request->body()->add( 188 | name: 'metadata', 189 | contents: json_encode($this->metadata, JSON_THROW_ON_ERROR) 190 | ); 191 | } 192 | 193 | return $request; 194 | } 195 | 196 | protected function addCustomId(PostUploadUrl|PostImage $request): PostUploadUrl|PostImage 197 | { 198 | if ($this->customId) { 199 | $request->body()->add( 200 | name: 'id', 201 | contents: $this->customId 202 | ); 203 | } 204 | 205 | return $request; 206 | } 207 | } 208 | --------------------------------------------------------------------------------