TmdbClient   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 304
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 39
eloc 76
c 1
b 0
f 0
dl 0
loc 304
rs 9.28

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getTvExternalIds() 0 3 1
A getMovieCredits() 0 3 1
A searchTv() 0 13 2
A isConfigured() 0 3 1
A getTvEpisode() 0 3 1
A getMovie() 0 9 2
A get() 0 38 5
A getInt() 0 3 3
A getTvShow() 0 9 2
A getNested() 0 13 4
A getString() 0 3 3
A getImageUrl() 0 7 2
A getTvAlternativeTitles() 0 3 1
A getTvSeason() 0 3 1
A getArray() 0 3 3
A searchMovies() 0 13 2
A __construct() 0 6 1
A getFloat() 0 3 3
A getMovieExternalIds() 0 3 1
1
<?php
2
3
namespace App\Services;
4
5
use Illuminate\Support\Facades\Cache;
6
use Illuminate\Support\Facades\Http;
7
use Illuminate\Support\Facades\Log;
8
9
/**
10
 * Custom TMDB (The Movie Database) API Client
11
 *
12
 * This service provides methods to interact with The Movie Database API
13
 * for fetching movie and TV show information.
14
 */
15
class TmdbClient
16
{
17
    protected const BASE_URL = 'https://api.themoviedb.org/3';
18
19
    protected const IMAGE_BASE_URL = 'https://image.tmdb.org/t/p';
20
21
    protected string $apiKey;
22
23
    protected int $timeout;
24
25
    protected int $retryTimes;
26
27
    protected int $retryDelay;
28
29
    public function __construct()
30
    {
31
        $this->apiKey = (string) config('tmdb.api_key', '');
32
        $this->timeout = (int) config('tmdb.timeout', 30);
33
        $this->retryTimes = (int) config('tmdb.retry_times', 3);
34
        $this->retryDelay = (int) config('tmdb.retry_delay', 100);
35
    }
36
37
    /**
38
     * Check if the API key is configured
39
     */
40
    public function isConfigured(): bool
41
    {
42
        return ! empty($this->apiKey);
43
    }
44
45
    /**
46
     * Make a GET request to the TMDB API
47
     *
48
     * @param  string  $endpoint  The API endpoint
49
     * @param  array  $params  Additional query parameters
50
     * @return array|null Response data or null on failure
51
     */
52
    protected function get(string $endpoint, array $params = []): ?array
53
    {
54
        if (! $this->isConfigured()) {
55
            Log::warning('TMDB API key is not configured');
56
57
            return null;
58
        }
59
60
        $params['api_key'] = $this->apiKey;
61
62
        try {
63
            $response = Http::timeout($this->timeout)
64
                ->retry($this->retryTimes, $this->retryDelay)
65
                ->get(self::BASE_URL.$endpoint, $params);
66
67
            if ($response->successful()) {
0 ignored issues
show
Bug introduced by
The method successful() does not exist on Illuminate\Http\Client\Promises\LazyPromise. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

67
            if ($response->/** @scrutinizer ignore-call */ successful()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
68
                return $response->json();
0 ignored issues
show
Bug introduced by
The method json() does not exist on Illuminate\Http\Client\Promises\LazyPromise. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

68
                return $response->/** @scrutinizer ignore-call */ json();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
69
            }
70
71
            // Handle specific error codes
72
            if ($response->status() === 404) {
0 ignored issues
show
Bug introduced by
The method status() does not exist on Illuminate\Http\Client\Promises\LazyPromise. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

72
            if ($response->/** @scrutinizer ignore-call */ status() === 404) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
73
                return null;
74
            }
75
76
            Log::warning('TMDB API request failed', [
77
                'endpoint' => $endpoint,
78
                'status' => $response->status(),
79
                'body' => $response->body(),
0 ignored issues
show
Bug introduced by
The method body() does not exist on Illuminate\Http\Client\Promises\LazyPromise. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

79
                'body' => $response->/** @scrutinizer ignore-call */ body(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
80
            ]);
81
82
            return null;
83
        } catch (\Throwable $e) {
84
            Log::warning('TMDB API request exception', [
85
                'endpoint' => $endpoint,
86
                'message' => $e->getMessage(),
87
            ]);
88
89
            return null;
90
        }
91
    }
92
93
    /**
94
     * Get the full image URL
95
     *
96
     * @param  string|null  $path  The image path from TMDB
97
     * @param  string  $size  The image size (w92, w154, w185, w342, w500, w780, original)
98
     */
99
    public function getImageUrl(?string $path, string $size = 'w500'): string
100
    {
101
        if (empty($path)) {
102
            return '';
103
        }
104
105
        return self::IMAGE_BASE_URL.'/'.$size.$path;
106
    }
107
108
    // =========================================================================
109
    // MOVIE METHODS
110
    // =========================================================================
111
112
    /**
113
     * Search for movies by title
114
     *
115
     * @param  string  $query  The search query
116
     * @param  int  $page  Page number for pagination
117
     * @param  string|null  $year  Filter by release year
118
     * @return array|null Search results or null on failure
119
     */
120
    public function searchMovies(string $query, int $page = 1, ?string $year = null): ?array
121
    {
122
        $params = [
123
            'query' => $query,
124
            'page' => $page,
125
            'include_adult' => false,
126
        ];
127
128
        if ($year !== null) {
129
            $params['year'] = $year;
130
        }
131
132
        return $this->get('/search/movie', $params);
133
    }
134
135
    /**
136
     * Get movie details by TMDB ID or IMDB ID
137
     *
138
     * @param  int|string  $id  The TMDB ID or IMDB ID (with 'tt' prefix)
139
     * @param  array  $appendToResponse  Additional data to append (e.g., ['credits', 'external_ids'])
140
     * @return array|null Movie data or null on failure
141
     */
142
    public function getMovie(int|string $id, array $appendToResponse = []): ?array
143
    {
144
        $params = [];
145
146
        if (! empty($appendToResponse)) {
147
            $params['append_to_response'] = implode(',', $appendToResponse);
148
        }
149
150
        return $this->get('/movie/'.$id, $params);
151
    }
152
153
    /**
154
     * Get movie credits (cast and crew)
155
     *
156
     * @param  int  $movieId  The TMDB movie ID
157
     * @return array|null Credits data or null on failure
158
     */
159
    public function getMovieCredits(int $movieId): ?array
160
    {
161
        return $this->get('/movie/'.$movieId.'/credits');
162
    }
163
164
    /**
165
     * Get movie external IDs (IMDB, etc.)
166
     *
167
     * @param  int  $movieId  The TMDB movie ID
168
     * @return array|null External IDs or null on failure
169
     */
170
    public function getMovieExternalIds(int $movieId): ?array
171
    {
172
        return $this->get('/movie/'.$movieId.'/external_ids');
173
    }
174
175
    // =========================================================================
176
    // TV SHOW METHODS
177
    // =========================================================================
178
179
    /**
180
     * Search for TV shows by title
181
     *
182
     * @param  string  $query  The search query
183
     * @param  int  $page  Page number for pagination
184
     * @param  int|null  $firstAirDateYear  Filter by first air date year
185
     * @return array|null Search results or null on failure
186
     */
187
    public function searchTv(string $query, int $page = 1, ?int $firstAirDateYear = null): ?array
188
    {
189
        $params = [
190
            'query' => $query,
191
            'page' => $page,
192
            'include_adult' => false,
193
        ];
194
195
        if ($firstAirDateYear !== null) {
196
            $params['first_air_date_year'] = $firstAirDateYear;
197
        }
198
199
        return $this->get('/search/tv', $params);
200
    }
201
202
    /**
203
     * Get TV show details by ID
204
     *
205
     * @param  int|string  $id  The TMDB TV show ID
206
     * @param  array  $appendToResponse  Additional data to append
207
     * @return array|null TV show data or null on failure
208
     */
209
    public function getTvShow(int|string $id, array $appendToResponse = []): ?array
210
    {
211
        $params = [];
212
213
        if (! empty($appendToResponse)) {
214
            $params['append_to_response'] = implode(',', $appendToResponse);
215
        }
216
217
        return $this->get('/tv/'.$id, $params);
218
    }
219
220
    /**
221
     * Get TV show external IDs (IMDB, TVDB, etc.)
222
     *
223
     * @param  int  $tvId  The TMDB TV show ID
224
     * @return array|null External IDs or null on failure
225
     */
226
    public function getTvExternalIds(int $tvId): ?array
227
    {
228
        return $this->get('/tv/'.$tvId.'/external_ids');
229
    }
230
231
    /**
232
     * Get TV show alternative titles
233
     *
234
     * @param  int  $tvId  The TMDB TV show ID
235
     * @return array|null Alternative titles or null on failure
236
     */
237
    public function getTvAlternativeTitles(int $tvId): ?array
238
    {
239
        return $this->get('/tv/'.$tvId.'/alternative_titles');
240
    }
241
242
    /**
243
     * Get TV season details
244
     *
245
     * @param  int  $tvId  The TMDB TV show ID
246
     * @param  int  $seasonNumber  The season number
247
     * @return array|null Season data or null on failure
248
     */
249
    public function getTvSeason(int $tvId, int $seasonNumber): ?array
250
    {
251
        return $this->get('/tv/'.$tvId.'/season/'.$seasonNumber);
252
    }
253
254
    /**
255
     * Get TV episode details
256
     *
257
     * @param  int  $tvId  The TMDB TV show ID
258
     * @param  int  $seasonNumber  The season number
259
     * @param  int  $episodeNumber  The episode number
260
     * @return array|null Episode data or null on failure
261
     */
262
    public function getTvEpisode(int $tvId, int $seasonNumber, int $episodeNumber): ?array
263
    {
264
        return $this->get('/tv/'.$tvId.'/season/'.$seasonNumber.'/episode/'.$episodeNumber);
265
    }
266
267
    // =========================================================================
268
    // HELPER METHODS FOR NULL-SAFE DATA EXTRACTION
269
    // =========================================================================
270
271
    /**
272
     * Safely get a string value from an array
273
     */
274
    public static function getString(array $data, string $key, string $default = ''): string
275
    {
276
        return isset($data[$key]) && is_string($data[$key]) ? $data[$key] : $default;
277
    }
278
279
    /**
280
     * Safely get an integer value from an array
281
     */
282
    public static function getInt(array $data, string $key, int $default = 0): int
283
    {
284
        return isset($data[$key]) && is_numeric($data[$key]) ? (int) $data[$key] : $default;
285
    }
286
287
    /**
288
     * Safely get a float value from an array
289
     */
290
    public static function getFloat(array $data, string $key, float $default = 0.0): float
291
    {
292
        return isset($data[$key]) && is_numeric($data[$key]) ? (float) $data[$key] : $default;
293
    }
294
295
    /**
296
     * Safely get an array value from an array
297
     */
298
    public static function getArray(array $data, string $key, array $default = []): array
299
    {
300
        return isset($data[$key]) && is_array($data[$key]) ? $data[$key] : $default;
301
    }
302
303
    /**
304
     * Safely get a nested value from an array using dot notation
305
     */
306
    public static function getNested(array $data, string $path, mixed $default = null): mixed
307
    {
308
        $keys = explode('.', $path);
309
        $value = $data;
310
311
        foreach ($keys as $key) {
312
            if (! is_array($value) || ! array_key_exists($key, $value)) {
313
                return $default;
314
            }
315
            $value = $value[$key];
316
        }
317
318
        return $value ?? $default;
319
    }
320
}
321
322