├── src ├── Exceptions │ ├── DirectusException.php │ └── AuthenticationException.php ├── Auth │ ├── AuthInterface.php │ ├── ApiKeyAuth.php │ ├── StaticTokenAuth.php │ └── UserPasswordAuth.php ├── Storage │ ├── StorageInterface.php │ ├── SessionStorage.php │ └── CookieStorage.php ├── Endpoints │ ├── UtilitiesEndpoint.php │ ├── ServerEndpoint.php │ ├── AbstractEndpoint.php │ ├── SchemaEndpoint.php │ ├── RevisionsEndpoint.php │ ├── ExtensionsEndpoint.php │ ├── ContentVersionsEndpoint.php │ ├── SettingsEndpoint.php │ ├── FlowsEndpoint.php │ ├── RolesEndpoint.php │ ├── PanelsEndpoint.php │ ├── SharesEndpoint.php │ ├── FoldersEndpoint.php │ ├── PresetsEndpoint.php │ ├── ActivityEndpoint.php │ ├── CommentsEndpoint.php │ ├── PoliciesEndpoint.php │ ├── RelationsEndpoint.php │ ├── DashboardsEndpoint.php │ ├── OperationsEndpoint.php │ ├── CollectionsEndpoint.php │ ├── PermissionsEndpoint.php │ ├── TranslationsEndpoint.php │ ├── FilesEndpoint.php │ ├── NotificationsEndpoint.php │ ├── FieldsEndpoint.php │ ├── ItemsEndpoint.php │ └── UsersEndpoint.php └── Directus.php ├── tests ├── TestCase.php ├── Pest.php └── Unit │ └── DirectusTest.php ├── .gitignore ├── phpunit.xml ├── composer.json ├── LICENSE └── README.md /src/Exceptions/DirectusException.php: -------------------------------------------------------------------------------- 1 | $string]; 10 | return $this->directus->makeCustomCall('/utils/hash', $data, 'POST'); 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS-specific files 2 | .DS_Store 3 | Thumbs.db 4 | 5 | # Dependency directories 6 | node_modules/ 7 | vendor/ 8 | 9 | # Log files 10 | *.log 11 | *.cache 12 | 13 | # Build and distribution 14 | dist/ 15 | build/ 16 | 17 | # Composer files 18 | composer.lock 19 | 20 | # IDE / Editor files 21 | .vscode/ 22 | .idea/ 23 | *.suo 24 | *.user 25 | *.sw? 26 | 27 | # Temporary files 28 | *.tmp 29 | *.bak 30 | *.old -------------------------------------------------------------------------------- /src/Endpoints/ServerEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/server/info', $data, 'GET'); 10 | } 11 | 12 | public function getPing($data = false) 13 | { 14 | return $this->directus->makeCustomCall('/server/ping', $data, 'GET'); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Endpoints/AbstractEndpoint.php: -------------------------------------------------------------------------------- 1 | directus = $directus; 16 | $this->client = $this->directus->getClient(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Auth/ApiKeyAuth.php: -------------------------------------------------------------------------------- 1 | apiKey = $apiKey; 12 | } 13 | 14 | public function authenticate(): string 15 | { 16 | return $this->apiKey; 17 | } 18 | 19 | public function refreshToken(): ?string 20 | { 21 | return null; // API Key doesn't support refresh 22 | } 23 | } -------------------------------------------------------------------------------- /src/Auth/StaticTokenAuth.php: -------------------------------------------------------------------------------- 1 | token = $token; 12 | } 13 | 14 | public function authenticate(): string 15 | { 16 | return $this->token; 17 | } 18 | 19 | public function refreshToken(): ?string 20 | { 21 | return null; // Static token doesn't support refresh 22 | } 23 | } -------------------------------------------------------------------------------- /src/Endpoints/SchemaEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/schema', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/schema/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/schema', false, 'GET'); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | ./src 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Endpoints/RevisionsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/revisions', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/revisions/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/revisions', false, 'GET'); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Endpoints/ExtensionsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/extensions', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/extensions/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/extensions', false, 'GET'); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Endpoints/ContentVersionsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/versions', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/versions/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/versions', false, 'GET'); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Storage/SessionStorage.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 12 | } 13 | 14 | public function set(string $key, $value): void 15 | { 16 | $_SESSION[$this->prefix . $key] = $value; 17 | } 18 | 19 | public function get(string $key) 20 | { 21 | return $_SESSION[$this->prefix . $key] ?? null; 22 | } 23 | 24 | public function delete(string $key): void 25 | { 26 | unset($_SESSION[$this->prefix . $key]); 27 | } 28 | } -------------------------------------------------------------------------------- /src/Endpoints/SettingsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/settings', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/settings/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/settings', false, 'GET'); 15 | } 16 | } 17 | 18 | public function update(string $id, array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/settings/' . $id, $fields, 'PATCH'); 21 | } 22 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alantiller/directus-php-sdk", 3 | "description": "A PHP SDK for interacting with the Directus API", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Alan Tiller", 9 | "email": "your.email@example.com" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { 14 | "AlanTiller\\DirectusSdk\\": "src/" 15 | } 16 | }, 17 | "require": { 18 | "php": "^8.0", 19 | "guzzlehttp/guzzle": "^7.0" 20 | }, 21 | "minimum-stability": "stable", 22 | "require-dev": { 23 | "pestphp/pest": "^3.7" 24 | }, 25 | "config": { 26 | "allow-plugins": { 27 | "pestphp/pest-plugin": true 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Storage/CookieStorage.php: -------------------------------------------------------------------------------- 1 | prefix = $prefix; 13 | $this->domain = $domain; 14 | } 15 | 16 | public function set(string $key, $value): void 17 | { 18 | setcookie($this->prefix . $key, $value, time() + 604800, '/', $this->domain); 19 | } 20 | 21 | public function get(string $key) 22 | { 23 | return $_COOKIE[$this->prefix . $key] ?? null; 24 | } 25 | 26 | public function delete(string $key): void 27 | { 28 | setcookie($this->prefix . $key, '', time() - 1, '/', $this->domain); 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alan Tiller 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Endpoints/FlowsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/flows', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/flows/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/flows', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/flows', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/flows/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/flows', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/flows/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/RolesEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/roles', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/roles/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/roles', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/roles', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/roles/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/roles', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/roles/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/PanelsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/panels', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/panels/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/panels', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/panels', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/panels/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/panels', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/panels/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/SharesEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/shares', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/shares/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/shares', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/shares', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/shares/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/shares', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/shares/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/FoldersEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/folders', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/folders/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/folders', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/folders', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/folders/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/folders', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/folders/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/PresetsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/presets', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/presets/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/presets', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/presets', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/presets/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/presets', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/presets/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/ActivityEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/activity', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/activity/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/activity', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/activity', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/activity/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/activity', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/activity/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/CommentsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/comments', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/comments/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/comments', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/comments', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/comments/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/comments', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/comments/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/PoliciesEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/policies', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/policies/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/policies', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/policies', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/policies/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/policies', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/policies/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/RelationsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/relations', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/relations/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/relations', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/relations', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/relations/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/relations', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/relations/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/DashboardsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/dashboards', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/dashboards/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/dashboards', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/dashboards', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/dashboards/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/dashboards', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/dashboards/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/OperationsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/operations', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/operations/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/operations', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/operations', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/operations/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/operations', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/operations/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/CollectionsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/collections', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/collections/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/collections', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/collections', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/collections/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/collections', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/collections/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/PermissionsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/permissions', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/permissions/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/permissions', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/permissions', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/permissions/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/permissions', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/permissions/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/TranslationsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/translations', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/translations/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/translations', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/translations', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/translations/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/translations', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/translations/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/FilesEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/files/' . $data, false, 'GET'); 11 | } else { 12 | return $this->directus->makeCustomCall('/files', false, 'GET'); 13 | } 14 | } 15 | 16 | public function create(array $file, string $folder = null, string $storage = 'local'): array 17 | { 18 | $data = [ 19 | 'folder' => $folder, 20 | 'file' => $file, 21 | 'storage' => $storage, 22 | ]; 23 | return $this->directus->makeCustomCall('/files', $data, 'POST_MULTIPART'); 24 | } 25 | 26 | public function update(string $id, array $fields): array 27 | { 28 | return $this->directus->makeCustomCall('/files/' . $id, $fields, 'PATCH'); 29 | } 30 | 31 | public function delete($id): array 32 | { 33 | if (is_array($id)) { 34 | return $this->directus->makeCustomCall('/files', $id, 'DELETE'); 35 | } else { 36 | return $this->directus->makeCustomCall('/files/' . $id, false, 'DELETE'); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Endpoints/NotificationsEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/notifications', $data, 'GET'); 11 | } elseif (is_integer($data) || is_string($data)) { 12 | return $this->directus->makeCustomCall('/notifications/' . $data, false, 'GET'); 13 | } else { 14 | return $this->directus->makeCustomCall('/notifications', false, 'GET'); 15 | } 16 | } 17 | 18 | public function create(array $fields): array 19 | { 20 | return $this->directus->makeCustomCall('/notifications', $fields, 'POST'); 21 | } 22 | 23 | public function update(string $id, array $fields): array 24 | { 25 | return $this->directus->makeCustomCall('/notifications/' . $id, $fields, 'PATCH'); 26 | } 27 | 28 | public function delete($id): array 29 | { 30 | if (is_array($id)) { 31 | return $this->directus->makeCustomCall('/notifications', $id, 'DELETE'); 32 | } else { 33 | return $this->directus->makeCustomCall('/notifications/' . $id, false, 'DELETE'); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Endpoints/FieldsEndpoint.php: -------------------------------------------------------------------------------- 1 | collection = $collection; 15 | } 16 | 17 | public function get($data = false) 18 | { 19 | if (is_array($data)) { 20 | return $this->directus->makeCustomCall('/fields/' . $this->collection, $data, 'GET'); 21 | } elseif (is_integer($data) || is_string($data)) { 22 | return $this->directus->makeCustomCall('/fields/' . $this->collection . '/' . $data, false, 'GET'); 23 | } else { 24 | return $this->directus->makeCustomCall('/fields/' . $this->collection, false, 'GET'); 25 | } 26 | } 27 | 28 | public function create(array $fields): array 29 | { 30 | return $this->directus->makeCustomCall('/fields/' . $this->collection, $fields, 'POST'); 31 | } 32 | 33 | public function update(string $id, array $fields): array 34 | { 35 | return $this->directus->makeCustomCall('/fields/' . $this->collection . '/' . $id, $fields, 'PATCH'); 36 | } 37 | 38 | public function delete($id): array 39 | { 40 | if (is_array($id)) { 41 | return $this->directus->makeCustomCall('/fields/' . $this->collection, $id, 'DELETE'); 42 | } else { 43 | return $this->directus->makeCustomCall('/fields/' . $this->collection . '/' . $id, false, 'DELETE'); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | extend(Tests\TestCase::class)->in('Feature'); 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Expectations 19 | |-------------------------------------------------------------------------- 20 | | 21 | | When you're writing tests, you often need to check that values meet certain conditions. The 22 | | "expect()" function gives you access to a set of "expectations" methods that you can use 23 | | to assert different things. Of course, you may extend the Expectation API at any time. 24 | | 25 | */ 26 | 27 | expect()->extend('toBeOne', function () { 28 | return $this->toBe(1); 29 | }); 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Functions 34 | |-------------------------------------------------------------------------- 35 | | 36 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 37 | | project that you don't want to repeat in every file. Here you can also expose helpers as 38 | | global functions to help you to reduce the number of lines of code in your test files. 39 | | 40 | */ 41 | 42 | function something() 43 | { 44 | // .. 45 | } 46 | -------------------------------------------------------------------------------- /src/Endpoints/ItemsEndpoint.php: -------------------------------------------------------------------------------- 1 | collection = $collection; 15 | } 16 | 17 | public function get($data = false) 18 | { 19 | if (is_array($data)) { 20 | return $this->directus->makeCustomCall( 21 | '/items/' . $this->collection, 22 | $data, 23 | 'GET' 24 | ); 25 | } elseif (is_integer($data) || is_string($data)) { 26 | return $this->directus->makeCustomCall( 27 | '/items/' . $this->collection . '/' . $data, 28 | false, 29 | 'GET' 30 | ); 31 | } else { 32 | return $this->directus->makeCustomCall( 33 | '/items/' . $this->collection, 34 | false, 35 | 'GET' 36 | ); 37 | } 38 | } 39 | 40 | public function create(array $fields): array 41 | { 42 | return $this->directus->makeCustomCall( 43 | '/items/' . $this->collection, 44 | $fields, 45 | 'POST' 46 | ); 47 | } 48 | 49 | public function update(array $fields, $id = null): array 50 | { 51 | $uri = '/items/' . $this->collection; 52 | if ($id != null) { 53 | $uri .= '/' . $id; 54 | } 55 | return $this->directus->makeCustomCall($uri, $fields, 'PATCH'); 56 | } 57 | 58 | public function delete($id): array 59 | { 60 | $uri = '/items/' . $this->collection; 61 | if (is_array($id)) { 62 | return $this->directus->makeCustomCall($uri, $id, 'DELETE'); 63 | } else { 64 | $uri .= '/' . $id; 65 | return $this->directus->makeCustomCall($uri, false, 'DELETE'); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/Endpoints/UsersEndpoint.php: -------------------------------------------------------------------------------- 1 | directus->makeCustomCall('/users', $data, 'GET'); 17 | } elseif (is_integer($data) || is_string($data)) { 18 | return $this->directus->makeCustomCall('/users/' . $data, false, 'GET'); 19 | } else { 20 | return $this->directus->makeCustomCall('/users', false, 'GET'); 21 | } 22 | } 23 | 24 | /** 25 | * Create a new user 26 | * 27 | * @param array $fields 28 | * @return array 29 | */ 30 | public function create(array $fields): array 31 | { 32 | return $this->directus->makeCustomCall('/users', $fields, 'POST'); 33 | } 34 | 35 | public function update(array $fields, $id = null): array 36 | { 37 | return $this->directus->makeCustomCall('/users/' . $id, $fields, 'PATCH'); 38 | } 39 | 40 | public function delete($id): array 41 | { 42 | if (is_array($id)) { 43 | return $this->directus->makeCustomCall('/users', $id, 'DELETE'); 44 | } else { 45 | return $this->directus->makeCustomCall('/users/' . $id, false, 'DELETE'); 46 | } 47 | } 48 | 49 | public function invite(string $email, string $role, string|bool $invite_url = false) 50 | { 51 | $data = ['email' => $email, 'role' => $role]; 52 | if ($invite_url != false) { 53 | $data['invite_url'] = $invite_url; 54 | } 55 | return $this->directus->makeCustomCall('/users/invite', $data, 'POST'); 56 | } 57 | 58 | public function acceptInvite(string $password, string $token) 59 | { 60 | $data = ['password' => $password, 'token' => $token]; 61 | return $this->directus->makeCustomCall('/users/invite/accept', $data, 'POST'); 62 | } 63 | 64 | public function me($filter = false) 65 | { 66 | return $this->directus->makeCustomCall('/users/me', $filter, 'GET'); 67 | } 68 | } -------------------------------------------------------------------------------- /src/Auth/UserPasswordAuth.php: -------------------------------------------------------------------------------- 1 | baseUrl = $baseUrl; 19 | $this->username = $username; 20 | $this->password = $password; 21 | } 22 | 23 | public function authenticate(): string 24 | { 25 | $client = new Client([ 26 | 'base_uri' => $this->baseUrl, 27 | 'headers' => [ 28 | 'Content-Type' => 'application/json', 29 | ], 30 | ]); 31 | 32 | try { 33 | $response = $client->post('/auth/login', [ 34 | 'json' => [ 35 | 'email' => $this->username, 36 | 'password' => $this->password, 37 | ], 38 | ]); 39 | 40 | $data = json_decode($response->getBody(), true); 41 | 42 | if (isset($data['data']['access_token']) && isset($data['data']['refresh_token'])) { 43 | $this->accessToken = $data['data']['access_token']; 44 | $this->refreshToken = $data['data']['refresh_token']; 45 | return $this->accessToken; 46 | } else { 47 | throw new AuthenticationException('Invalid credentials'); 48 | } 49 | } catch (\Exception $e) { 50 | throw new AuthenticationException('Authentication failed: ' . $e->getMessage()); 51 | } 52 | } 53 | 54 | public function refreshToken(): ?string 55 | { 56 | if (!$this->refreshToken) { 57 | return null; 58 | } 59 | 60 | $client = new Client([ 61 | 'base_uri' => $this->baseUrl, 62 | 'headers' => [ 63 | 'Content-Type' => 'application/json', 64 | ], 65 | ]); 66 | 67 | try { 68 | $response = $client->post('/auth/refresh', [ 69 | 'json' => [ 70 | 'refresh_token' => $this->refreshToken, 71 | ], 72 | ]); 73 | 74 | $data = json_decode($response->getBody(), true); 75 | 76 | if (isset($data['data']['access_token']) && isset($data['data']['refresh_token'])) { 77 | $this->accessToken = $data['data']['access_token']; 78 | $this->refreshToken = $data['data']['refresh_token']; 79 | return $this->accessToken; 80 | } else { 81 | return null; 82 | } 83 | } catch (\Exception $e) { 84 | return null; 85 | } 86 | } 87 | 88 | public function getRefreshToken(): ?string 89 | { 90 | return $this->refreshToken; 91 | } 92 | } -------------------------------------------------------------------------------- /tests/Unit/DirectusTest.php: -------------------------------------------------------------------------------- 1 | $data], JSON_THROW_ON_ERROR)); 20 | } 21 | 22 | // Helper function to create a Directus instance with a mock client 23 | function createMockDirectus(array $responses): Directus 24 | { 25 | $mock = new MockHandler($responses); 26 | $handlerStack = HandlerStack::create($mock); 27 | $client = new Client(['handler' => $handlerStack]); 28 | 29 | $storage = new SessionStorage('test_'); 30 | $directus = new Directus( 31 | 'https://example.directus.com', 32 | $storage, 33 | null, 34 | true 35 | ); 36 | $reflection = new \ReflectionClass($directus); 37 | $clientProperty = $reflection->getProperty('client'); 38 | $clientProperty->setAccessible(true); 39 | $clientProperty->setValue($directus, $client); 40 | 41 | return $directus; 42 | } 43 | 44 | it('can create a Directus instance', function () { 45 | $storage = new SessionStorage('test_'); 46 | $directus = new Directus( 47 | 'https://example.directus.com', 48 | $storage, 49 | null, 50 | true 51 | ); 52 | expect($directus)->toBeInstanceOf(Directus::class); 53 | }); 54 | 55 | it('can set and get the base URL', function () { 56 | $storage = new SessionStorage('test_'); 57 | $directus = new Directus( 58 | 'https://example.directus.com', 59 | $storage, 60 | null, 61 | true 62 | ); 63 | expect($directus->getBaseUrl())->toBe('https://example.directus.com'); 64 | }); 65 | 66 | it('can get the Guzzle client', function () { 67 | $storage = new SessionStorage('test_'); 68 | $directus = new Directus( 69 | 'https://example.directus.com', 70 | $storage, 71 | null, 72 | true 73 | ); 74 | expect($directus->getClient())->toBeInstanceOf(Client::class); 75 | }); 76 | 77 | it('can create an ItemsEndpoint instance', function () { 78 | $storage = new SessionStorage('test_'); 79 | $directus = new Directus( 80 | 'https://example.directus.com', 81 | $storage, 82 | null, 83 | true 84 | ); 85 | $items = $directus->items('test_collection'); 86 | expect($items)->toBeInstanceOf(ItemsEndpoint::class); 87 | }); 88 | 89 | it('can get items from a collection', function () { 90 | $responses = [ 91 | createMockResponse(200, [['id' => 1, 'name' => 'Test Item']]), 92 | ]; 93 | $directus = createMockDirectus($responses); 94 | 95 | $items = $directus->items('test_collection')->get(); 96 | 97 | expect($items['data'])->toBeArray(); 98 | expect($items['data'][0]['id'])->toBe(1); 99 | expect($items['data'][0]['name'])->toBe('Test Item'); 100 | }); 101 | 102 | it('can create items in a collection', function () { 103 | $responses = [ 104 | createMockResponse(200, ['id' => 2, 'name' => 'New Item']), 105 | ]; 106 | $directus = createMockDirectus($responses); 107 | 108 | $items = $directus->items('test_collection')->create(['name' => 'New Item']); 109 | 110 | expect($items['data'])->toBeArray(); 111 | expect($items['data']['id'])->toBe(2); 112 | expect($items['data']['name'])->toBe('New Item'); 113 | }); 114 | 115 | it('can update items in a collection', function () { 116 | $responses = [ 117 | createMockResponse(200, ['id' => 1, 'name' => 'Updated Item']), 118 | ]; 119 | $directus = createMockDirectus($responses); 120 | 121 | $items = $directus->items('test_collection')->update(['name' => 'Updated Item'], 1); 122 | 123 | expect($items['data'])->toBeArray(); 124 | expect($items['data']['id'])->toBe(1); 125 | expect($items['data']['name'])->toBe('Updated Item'); 126 | }); 127 | 128 | it('can delete items from a collection', function () { 129 | $responses = [ 130 | new Response(204, [], null), // 204 No Content for successful deletion 131 | ]; 132 | $directus = createMockDirectus($responses); 133 | 134 | $items = $directus->items('test_collection')->delete(1); 135 | 136 | expect($items['status'])->toBe(204); 137 | }); 138 | 139 | it('can authenticate with an API key', function () { 140 | $requestUri = ''; 141 | $requestHeaders = []; 142 | 143 | $mock = new MockHandler([ 144 | new Response(200, [], json_encode(['data' => []])), 145 | ]); 146 | 147 | $handlerStack = HandlerStack::create($mock); 148 | $client = new Client(['handler' => $handlerStack]); 149 | 150 | $storage = new SessionStorage('test_'); 151 | $auth = new ApiKeyAuth('test_api_key'); 152 | $directus = new Directus( 153 | 'https://example.directus.com', 154 | $storage, 155 | $auth, 156 | true 157 | ); 158 | 159 | $reflection = new \ReflectionClass($directus); 160 | $clientProperty = $reflection->getProperty('client'); 161 | $clientProperty->setAccessible(true); 162 | $clientProperty->setValue($directus, $client); 163 | 164 | $handlerStack->push(function (callable $handler) use (&$requestUri, &$requestHeaders) { 165 | return function (Request $request, array $options) use ($handler, &$requestUri, &$requestHeaders) { 166 | $requestUri = (string) $request->getUri(); 167 | $requestHeaders = $request->getHeaders(); 168 | return $handler($request, $options); 169 | }; 170 | }); 171 | 172 | $directus->items('test_collection')->get(); 173 | 174 | expect($requestHeaders['Authorization'][0])->toBe('Bearer test_api_key'); 175 | expect($requestUri)->toBe('https://example.directus.com/items/test_collection'); 176 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Directus PHP SDK 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | A PHP SDK for interacting with the Directus API. This SDK provides a convenient and object-oriented way to access Directus endpoints and perform common operations. 6 | 7 | ## Table of Contents 8 | 9 | - [Directus PHP SDK](#directus-php-sdk) 10 | - [Table of Contents](#table-of-contents) 11 | - [Features](#features) 12 | - [Requirements](#requirements) 13 | - [Installation](#installation) 14 | - [Configuration](#configuration) 15 | - [Usage](#usage) 16 | - [Authentication](#authentication) 17 | - [API Key Authentication](#api-key-authentication) 18 | - [User/Password Authentication](#userpassword-authentication) 19 | - [Items](#items) 20 | - [Users](#users) 21 | - [Files](#files) 22 | - [Other Endpoints](#other-endpoints) 23 | - [Custom Calls](#custom-calls) 24 | - [Storage](#storage) 25 | - [Session Storage](#session-storage) 26 | - [Cookie Storage](#cookie-storage) 27 | - [Custom Storage](#custom-storage) 28 | - [Error Handling](#error-handling) 29 | - [Testing](#testing) 30 | - [Contributing](#contributing) 31 | - [License](#license) 32 | 33 | ## Features 34 | 35 | - Object-oriented interface for interacting with the Directus API 36 | - Supports all Directus endpoints (Items, Files, Users, etc.) 37 | - Supports multiple authentication methods (API Key, User/Password) 38 | - Customizable storage for authentication tokens (Session, Cookie, Custom) 39 | - Easy-to-use methods for common CRUD operations (Create, Read, Update, Delete) 40 | - Comprehensive error handling 41 | 42 | ## Requirements 43 | 44 | - PHP 8.0 or higher 45 | - Composer 46 | - Guzzle HTTP client (`guzzlehttp/guzzle`) 47 | 48 | ## Installation 49 | 50 | 1. Install the SDK using Composer: 51 | 52 | ```bash 53 | composer require alantiller/directus-php-sdk 54 | ``` 55 | 56 | ## Configuration 57 | 58 | Before using the SDK, you need to configure it with your Directus base URL and authentication details. 59 | 60 | 1. **Base URL:** The base URL of your Directus instance (e.g., `https://your-directus-instance.com`). 61 | 2. **Storage:** Choose a storage mechanism for authentication tokens (Session, Cookie, or Custom). 62 | 3. **Authentication:** Choose an authentication method and provide the necessary credentials. 63 | 64 | ## Usage 65 | 66 | ### Authentication 67 | 68 | The SDK supports multiple authentication methods: 69 | 70 | #### API Key Authentication 71 | 72 | ```php 73 | use AlanTiller\DirectusSdk\Directus; 74 | use AlanTiller\DirectusSdk\Auth\ApiKeyAuth; 75 | use AlanTiller\DirectusSdk\Storage\SessionStorage; 76 | 77 | $baseUrl = 'https://your-directus-instance.com'; 78 | $apiKey = 'YOUR_API_KEY'; 79 | 80 | $storage = new SessionStorage('directus_'); // Optional prefix 81 | $auth = new ApiKeyAuth($apiKey); 82 | 83 | $directus = new Directus( 84 | $baseUrl, 85 | $storage, 86 | $auth 87 | ); 88 | ``` 89 | 90 | #### User/Password Authentication 91 | 92 | ```php 93 | use AlanTiller\DirectusSdk\Directus; 94 | use AlanTiller\DirectusSdk\Auth\UserPasswordAuth; 95 | use AlanTiller\DirectusSdk\Storage\SessionStorage; 96 | 97 | $baseUrl = 'https://your-directus-instance.com'; 98 | $username = 'your_email@example.com'; 99 | $password = 'your_password'; 100 | 101 | $storage = new SessionStorage('directus_'); // Optional prefix 102 | $auth = new UserPasswordAuth($baseUrl, $username, $password); 103 | 104 | $directus = new Directus( 105 | $baseUrl, 106 | $storage, 107 | $auth 108 | ); 109 | 110 | // Authenticate the user 111 | try { 112 | $directus->authenticate(); 113 | } catch (\Exception $e) { 114 | echo "Authentication failed: " . $e->getMessage() . PHP_EOL; 115 | } 116 | ``` 117 | 118 | ### Items 119 | 120 | The `items` endpoint allows you to manage items in a specific collection. 121 | 122 | ```php 123 | use AlanTiller\DirectusSdk\Directus; 124 | use AlanTiller\DirectusSdk\Storage\SessionStorage; 125 | 126 | $baseUrl = 'https://your-directus-instance.com'; 127 | $storage = new SessionStorage('directus_'); 128 | 129 | $directus = new Directus( 130 | $baseUrl, 131 | $storage 132 | ); 133 | 134 | $collection = 'your_collection'; 135 | $items = $directus->items($collection); 136 | 137 | // Get all items 138 | $all_items = $items->get(); 139 | print_r($all_items); 140 | 141 | // Get a specific item 142 | $item = $items->get(1); 143 | print_r($item); 144 | 145 | // Create a new item 146 | $new_item = $items->create(['name' => 'New Item', 'status' => 'published']); 147 | print_r($new_item); 148 | 149 | // Update an existing item 150 | $updated_item = $items->update(['name' => 'Updated Item'], 1); 151 | print_r($updated_item); 152 | 153 | // Delete an item 154 | $deleted_item = $items->delete(1); 155 | print_r($deleted_item); 156 | ``` 157 | 158 | ### Users 159 | 160 | The `users` endpoint allows you to manage users in your Directus instance. 161 | 162 | ```php 163 | use AlanTiller\DirectusSdk\Directus; 164 | use AlanTiller\DirectusSdk\Storage\SessionStorage; 165 | 166 | $baseUrl = 'https://your-directus-instance.com'; 167 | $storage = new SessionStorage('directus_'); 168 | 169 | $directus = new Directus( 170 | $baseUrl, 171 | $storage 172 | ); 173 | 174 | $users = $directus->users(); 175 | 176 | // Get all users 177 | $all_users = $users->get(); 178 | print_r($all_users); 179 | 180 | // Get a specific user 181 | $user = $users->get('user_id'); 182 | print_r($user); 183 | 184 | // Create a new user 185 | $new_user = $users->create([ 186 | 'first_name' => 'John', 187 | 'last_name' => 'Doe', 188 | 'email' => 'john.doe@example.com', 189 | 'password' => 'password123', 190 | 'role' => 'administrator' 191 | ]); 192 | print_r($new_user); 193 | 194 | // Update an existing user 195 | $updated_user = $users->update([ 196 | 'first_name' => 'Jane', 197 | 'last_name' => 'Doe' 198 | ], 'user_id'); 199 | print_r($updated_user); 200 | 201 | // Delete a user 202 | $deleted_user = $users->delete('user_id'); 203 | print_r($deleted_user); 204 | ``` 205 | 206 | ### Files 207 | 208 | The `files` endpoint allows you to manage files in your Directus instance. 209 | 210 | ```php 211 | use AlanTiller\DirectusSdk\Directus; 212 | use AlanTiller\DirectusSdk\Storage\SessionStorage; 213 | 214 | $baseUrl = 'https://your-directus-instance.com'; 215 | $storage = new SessionStorage('directus_'); 216 | 217 | $directus = new Directus( 218 | $baseUrl, 219 | $storage 220 | ); 221 | 222 | $files = $directus->files(); 223 | 224 | // Get all files 225 | $all_files = $files->get(); 226 | print_r($all_files); 227 | 228 | // Get a specific file 229 | $file = $files->get('file_id'); 230 | print_r($file); 231 | 232 | // Create a new file 233 | $file_path = '/path/to/your/file.jpg'; 234 | $new_file = $files->create([ 235 | 'name' => basename($file_path), 236 | 'tmp_name' => $file_path, 237 | ]); 238 | print_r($new_file); 239 | 240 | // Update an existing file 241 | $updated_file = $files->update('file_id', ['title' => 'New Title']); 242 | print_r($updated_file); 243 | 244 | // Delete a file 245 | $deleted_file = $files->delete('file_id'); 246 | print_r($deleted_file); 247 | 248 | // Update complete file (uses custom call) 249 | $file_path = '/path/to/your/file.jpg'; 250 | $this->directus->makeCustomCall( 251 | sprintf('/files/%s', $fileId), 252 | [ 253 | 'name' => basename($file_path), 254 | 'tmp_name' => $file_path, 255 | ], 256 | 'PATCH_MULTIPART' 257 | ); 258 | ``` 259 | 260 | ### Other Endpoints 261 | 262 | The SDK provides access to all Directus endpoints, including: 263 | 264 | - `activity()` 265 | - `collections()` 266 | - `comments()` 267 | - `contentVersions()` 268 | - `dashboards()` 269 | - `extensions()` 270 | - `fields(string $collection)` 271 | - `flows()` 272 | - `folders()` 273 | - `notifications()` 274 | - `operations()` 275 | - `panels()` 276 | - `permissions()` 277 | - `policies()` 278 | - `presets()` 279 | - `relations()` 280 | - `revisions()` 281 | - `roles()` 282 | - `schema()` 283 | - `server()` 284 | - `settings()` 285 | - `shares()` 286 | - `translations()` 287 | - `utilities()` 288 | 289 | Each endpoint provides methods for performing common operations, such as `get`, `create`, `update`, and `delete`. Refer to the Directus API documentation for more information on each endpoint and its available methods. 290 | 291 | ### Custom Calls 292 | 293 | You can make custom API calls using the `makeCustomCall` method: 294 | 295 | ```php 296 | use AlanTiller\DirectusSdk\Directus; 297 | use AlanTiller\DirectusSdk\Storage\SessionStorage; 298 | 299 | $baseUrl = 'https://your-directus-instance.com'; 300 | $storage = new SessionStorage('directus_'); 301 | 302 | $directus = new Directus( 303 | $baseUrl, 304 | $storage 305 | ); 306 | 307 | $uri = '/your/custom/endpoint'; 308 | $data = ['param1' => 'value1', 'param2' => 'value2']; 309 | $method = 'GET'; 310 | 311 | $response = $directus->makeCustomCall($uri, $data, $method); 312 | print_r($response); 313 | ``` 314 | 315 | ## Storage 316 | 317 | The SDK uses a `StorageInterface` to store authentication tokens. You can choose between session storage, cookie storage, or implement your own custom storage mechanism. 318 | 319 | ### Session Storage 320 | 321 | Session storage uses PHP sessions to store authentication tokens. This is the default storage mechanism. 322 | 323 | ```php 324 | use AlanTiller\DirectusSdk\Storage\SessionStorage; 325 | 326 | $storage = new SessionStorage('directus_'); // Optional prefix 327 | ``` 328 | 329 | ### Cookie Storage 330 | 331 | Cookie storage uses cookies to store authentication tokens. 332 | 333 | ```php 334 | use AlanTiller\DirectusSdk\Storage\CookieStorage; 335 | 336 | $storage = new CookieStorage('directus_', '/'); // Optional prefix and domain 337 | ``` 338 | 339 | ### Custom Storage 340 | 341 | You can implement your own custom storage mechanism by creating a class that implements the `StorageInterface`. 342 | 343 | ```php 344 | use AlanTiller\DirectusSdk\Storage\StorageInterface; 345 | 346 | class MyCustomStorage implements StorageInterface 347 | { 348 | public function set(string $key, $value): void 349 | { 350 | // Store the value 351 | } 352 | 353 | public function get(string $key) 354 | { 355 | // Retrieve the value 356 | } 357 | 358 | public function delete(string $key): void 359 | { 360 | // Delete the value 361 | } 362 | } 363 | 364 | $storage = new MyCustomStorage(); 365 | ``` 366 | 367 | ## Error Handling 368 | 369 | The SDK throws exceptions for API errors. You can catch these exceptions and handle them appropriately. 370 | 371 | ```php 372 | use AlanTiller\DirectusSdk\Directus; 373 | use AlanTiller\DirectusSdk\Storage\SessionStorage; 374 | use AlanTiller\DirectusSdk\Exceptions\DirectusException; 375 | 376 | $baseUrl = 'https://your-directus-instance.com'; 377 | $storage = new SessionStorage('directus_'); 378 | 379 | $directus = new Directus( 380 | $baseUrl, 381 | $storage 382 | ); 383 | 384 | try { 385 | $items = $directus->items('your_collection')->get(); 386 | print_r($items); 387 | } catch (DirectusException $e) { 388 | echo "API error: " . $e->getMessage() . PHP_EOL; 389 | } 390 | ``` 391 | 392 | ## Testing 393 | 394 | The SDK includes a set of Pest PHP tests to ensure that it functions correctly. To run the tests, follow these steps: 395 | 396 | 1. Install Pest PHP: 397 | 398 | ```bash 399 | composer require pestphp/pest --dev 400 | ``` 401 | 402 | 2. Run the tests: 403 | 404 | ```bash 405 | ./vendor/bin/pest 406 | ``` 407 | 408 | ## Contributing 409 | 410 | Contributions are welcome! Please submit a pull request with your changes. 411 | 412 | ## License 413 | 414 | The Directus PHP SDK is licensed under the [MIT License](LICENSE). 415 | -------------------------------------------------------------------------------- /src/Directus.php: -------------------------------------------------------------------------------- 1 | baseUrl = rtrim($baseUrl, '/'); 58 | $this->storage = $storage; 59 | $this->auth = $auth; 60 | $this->stripHeaders = $stripHeaders; 61 | 62 | $this->client = new Client([ 63 | 'base_uri' => $this->baseUrl, 64 | 'headers' => [ 65 | 'Content-Type' => 'application/json', 66 | 'Accept' => 'application/json', 67 | ], 68 | ]); 69 | 70 | // Attempt to authenticate if possible 71 | if ($this->auth) { 72 | try { 73 | $this->authenticate(); 74 | } catch (\Exception $e) { 75 | // Handle authentication failure (e.g., log the error) 76 | error_log('Authentication failed during Directus instantiation: ' . $e->getMessage()); 77 | } 78 | } 79 | } 80 | 81 | public function authenticate(): void 82 | { 83 | if ($this->auth) { 84 | $this->accessToken = $this->auth->authenticate(); 85 | $this->client = new Client([ 86 | 'base_uri' => $this->baseUrl, 87 | 'headers' => [ 88 | 'Content-Type' => 'application/json', 89 | 'Accept' => 'application/json', 90 | 'Authorization' => 'Bearer ' . $this->accessToken, 91 | ], 92 | ]); 93 | } 94 | } 95 | 96 | public function refreshToken(): void 97 | { 98 | if ($this->auth instanceof UserPasswordAuth) { 99 | $newToken = $this->auth->refreshToken(); 100 | if ($newToken) { 101 | $this->accessToken = $newToken; 102 | $this->client = new Client([ 103 | 'base_uri' => $this->baseUrl, 104 | 'headers' => [ 105 | 'Content-Type' => 'application/json', 106 | 'Accept' => 'application/json', 107 | 'Authorization' => 'Bearer ' . $this->accessToken, 108 | ], 109 | ]); 110 | } 111 | } 112 | } 113 | 114 | private function getAccessToken() 115 | { 116 | if ($this->storage->get('directus_refresh') != null) { 117 | if ($this->storage->get('directus_access_expires') < time()) { 118 | $refresh = curl_init($this->baseUrl . '/auth/refresh'); 119 | curl_setopt($refresh, CURLOPT_POST, 1); 120 | curl_setopt( 121 | $refresh, 122 | CURLOPT_POSTFIELDS, 123 | json_encode(['refresh_token' => $this->storage->get('directus_refresh')]) 124 | ); 125 | curl_setopt($refresh, CURLOPT_RETURNTRANSFER, 1); 126 | curl_setopt($refresh, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); 127 | $response = curl_exec($refresh); 128 | $httpcode = curl_getinfo($refresh, CURLINFO_HTTP_CODE); 129 | curl_close($refresh); 130 | if ($httpcode == 200) { 131 | $response = json_decode($response, true); 132 | $this->storage->set('directus_refresh', $response['data']['refresh_token']); 133 | $this->storage->set('directus_access', $response['data']['access_token']); 134 | $expires = $response['data']['expires'] / 1000; 135 | $expires = time() + $expires; 136 | $this->storage->set('directus_access_expires', $expires); 137 | return $response['data']['access_token']; 138 | } else { 139 | $this->authLogout(); 140 | return false; 141 | } 142 | } 143 | return $this->storage->get('directus_access'); 144 | } elseif ($this->accessToken) { 145 | return $this->accessToken; 146 | } else { 147 | return false; 148 | } 149 | } 150 | 151 | private function stripHeaders(array $response): array 152 | { 153 | if ($this->stripHeaders === false) { 154 | return $response; 155 | } else { 156 | unset($response['headers']); 157 | return $response; 158 | } 159 | } 160 | 161 | private function makeCall(string $request, mixed $data = false, string $method = 'GET', bool $bypass = false, bool $multipart = false) 162 | { 163 | $request = $this->baseUrl . $request; // add the base url to the requested uri 164 | 165 | $headers = [ 166 | 'Content-Type' => 'application/json', 167 | 'Accept' => 'application/json', 168 | ]; 169 | 170 | if (($this->accessToken != false || $this->storage->get('directus_refresh')) && $bypass == false) { 171 | $accessToken = $this->getAccessToken(); 172 | if ($accessToken) { 173 | $headers['Authorization'] = 'Bearer ' . $accessToken; 174 | } 175 | } 176 | 177 | $options = [ 178 | 'headers' => $headers, 179 | ]; 180 | 181 | try { 182 | switch ($method) { 183 | case 'POST': 184 | $options['json'] = $data; 185 | $response = $this->client->post($request, $options); 186 | break; 187 | case 'DELETE': 188 | $options['json'] = $data; 189 | $response = $this->client->delete($request, $options); 190 | break; 191 | case 'PATCH': 192 | $options['json'] = $data; 193 | $response = $this->client->patch($request, $options); 194 | break; 195 | case 'POST_MULTIPART': 196 | $multipartData = []; 197 | foreach ($data as $key => $value) { 198 | if ($key === 'file') { 199 | $multipartData[] = [ 200 | 'name' => $value['name'], 201 | 'contents' => fopen($value['tmp_name'], 'r'), 202 | 'filename' => $value['name'], 203 | ]; 204 | } else { 205 | $multipartData[] = [ 206 | 'name' => $key, 207 | 'contents' => $value, 208 | ]; 209 | } 210 | } 211 | $options['multipart'] = $multipartData; 212 | // Remove Content-Type header for multipart requests 213 | unset($options['headers']['Content-Type']); 214 | $response = $this->client->post($request, $options); 215 | break; 216 | case 'PATCH_MULTIPART': 217 | $multipartData = []; 218 | foreach ($data as $key => $value) { 219 | if ($key === 'file') { 220 | $multipartData[] = [ 221 | 'name' => $value['name'], 222 | 'contents' => fopen($value['tmp_name'], 'r'), 223 | 'filename' => $value['name'], 224 | ]; 225 | } else { 226 | $multipartData[] = [ 227 | 'name' => $key, 228 | 'contents' => $value, 229 | ]; 230 | } 231 | } 232 | $options['multipart'] = $multipartData; 233 | // Remove Content-Type header for multipart requests 234 | unset($options['headers']['Content-Type']); 235 | $response = $this->client->patch($request, $options); 236 | break; 237 | default: // GET 238 | if ($data) { 239 | $options['query'] = $data; 240 | } 241 | $response = $this->client->get($request, $options); 242 | } 243 | 244 | return $this->processResponse($response); 245 | } catch (\Exception $e) { 246 | // Handle Guzzle exceptions (e.g., network errors, timeouts) 247 | throw new DirectusException('API request failed: ' . $e->getMessage(), $e->getCode(), $e); 248 | } 249 | } 250 | 251 | private function processResponse(ResponseInterface $response): array 252 | { 253 | $statusCode = $response->getStatusCode(); 254 | $body = $response->getBody()->getContents(); 255 | $data = json_decode($body, true); 256 | 257 | $headers = $response->getHeaders(); 258 | 259 | $result = [ 260 | 'data' => $data, 261 | 'headers' => $headers, 262 | 'status' => $statusCode, 263 | ]; 264 | 265 | if ($statusCode >= 400) { 266 | // Handle API errors (e.g., log the error, throw an exception) 267 | error_log('API error: ' . $body); 268 | // Optionally, throw an exception with the error details 269 | throw new DirectusException('API error: ' . $body, $statusCode); 270 | } 271 | 272 | return $result; 273 | } 274 | 275 | public function authLogout() 276 | { 277 | $data = ['refresh_token' => $this->storage->get('directus_refresh')]; 278 | $response = $this->makeCall('/auth/logout', $data, 'POST', true); 279 | if ($response['status'] === 204) { 280 | $this->storage->delete('directus_refresh'); 281 | $this->storage->delete('directus_access'); 282 | $this->storage->delete('directus_access_expires'); 283 | header('Refresh:0'); 284 | return true; 285 | } else { 286 | return $this->stripHeaders($response); 287 | } 288 | } 289 | 290 | public function authPasswordRequest(string $email, string $resetUrl = ''): array 291 | { 292 | $data = ['email' => $email]; 293 | if ($resetUrl != '') { 294 | $data['reset_url'] = $resetUrl; 295 | } 296 | return $this->makeCall('/auth/password/request', $data, 'POST'); 297 | } 298 | 299 | public function authPasswordReset(string $token, string $password): array 300 | { 301 | $data = ['token' => $token, 'password' => $password]; 302 | return $this->makeCall('/auth/password/reset', $data, 'POST'); 303 | } 304 | 305 | public function getBaseUrl(): string 306 | { 307 | return $this->baseUrl; 308 | } 309 | 310 | public function getClient(): Client 311 | { 312 | return $this->client; 313 | } 314 | 315 | public function makeCustomCall(string $uri, $data = false, string $method = 'GET'): array 316 | { 317 | return $this->makeCall($uri, $data, $method); 318 | } 319 | 320 | public function items(string $collection): ItemsEndpoint 321 | { 322 | return new ItemsEndpoint($this, $collection); 323 | } 324 | 325 | public function files(): FilesEndpoint 326 | { 327 | return new FilesEndpoint($this); 328 | } 329 | 330 | public function activity(): ActivityEndpoint 331 | { 332 | return new ActivityEndpoint($this); 333 | } 334 | 335 | public function collections(): CollectionsEndpoint 336 | { 337 | return new CollectionsEndpoint($this); 338 | } 339 | 340 | public function comments(): CommentsEndpoint 341 | { 342 | return new CommentsEndpoint($this); 343 | } 344 | 345 | public function contentVersions(): ContentVersionsEndpoint 346 | { 347 | return new ContentVersionsEndpoint($this); 348 | } 349 | 350 | public function dashboards(): DashboardsEndpoint 351 | { 352 | return new DashboardsEndpoint($this); 353 | } 354 | 355 | public function extensions(): ExtensionsEndpoint 356 | { 357 | return new ExtensionsEndpoint($this); 358 | } 359 | 360 | public function fields(string $collection): FieldsEndpoint 361 | { 362 | return new FieldsEndpoint($this, $collection); 363 | } 364 | 365 | public function flows(): FlowsEndpoint 366 | { 367 | return new FlowsEndpoint($this); 368 | } 369 | 370 | public function folders(): FoldersEndpoint 371 | { 372 | return new FoldersEndpoint($this); 373 | } 374 | 375 | public function notifications(): NotificationsEndpoint 376 | { 377 | return new NotificationsEndpoint($this); 378 | } 379 | 380 | public function operations(): OperationsEndpoint 381 | { 382 | return new OperationsEndpoint($this); 383 | } 384 | 385 | public function panels(): PanelsEndpoint 386 | { 387 | return new PanelsEndpoint($this); 388 | } 389 | 390 | public function permissions(): PermissionsEndpoint 391 | { 392 | return new PermissionsEndpoint($this); 393 | } 394 | 395 | public function policies(): PoliciesEndpoint 396 | { 397 | return new PoliciesEndpoint($this); 398 | } 399 | 400 | public function presets(): PresetsEndpoint 401 | { 402 | return new PresetsEndpoint($this); 403 | } 404 | 405 | public function relations(): RelationsEndpoint 406 | { 407 | return new RelationsEndpoint($this); 408 | } 409 | 410 | public function revisions(): RevisionsEndpoint 411 | { 412 | return new RevisionsEndpoint($this); 413 | } 414 | 415 | public function roles(): RolesEndpoint 416 | { 417 | return new RolesEndpoint($this); 418 | } 419 | 420 | public function schema(): SchemaEndpoint 421 | { 422 | return new SchemaEndpoint($this); 423 | } 424 | 425 | public function server(): ServerEndpoint 426 | { 427 | return new ServerEndpoint($this); 428 | } 429 | 430 | public function settings(): SettingsEndpoint 431 | { 432 | return new SettingsEndpoint($this); 433 | } 434 | 435 | public function shares(): SharesEndpoint 436 | { 437 | return new SharesEndpoint($this); 438 | } 439 | 440 | public function translations(): TranslationsEndpoint 441 | { 442 | return new TranslationsEndpoint($this); 443 | } 444 | 445 | public function users(): UsersEndpoint 446 | { 447 | return new UsersEndpoint($this); 448 | } 449 | 450 | public function utilities(): UtilitiesEndpoint 451 | { 452 | return new UtilitiesEndpoint($this); 453 | } 454 | } 455 | --------------------------------------------------------------------------------