├── phpstan.neon
├── phpcs.xml.dist
├── Resources
├── config
│ ├── twig.xml
│ ├── repositories.xml
│ └── services.xml
└── test
│ └── configuration.json
├── psalm.xml
├── phpunit.xml.dist
├── LICENSE
├── Twig
└── TmdbExtension.php
├── ClientConfiguration.php
├── composer.json
├── TmdbSymfonyBundle.php
├── DependencyInjection
├── CompilerPass
│ ├── ConfigurationPass.php
│ └── EventDispatchingPass.php
├── TmdbSymfonyExtension.php
└── Configuration.php
└── README.md
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 2
3 | inferPrivatePropertyTypeFromConstructor: true
4 | paths:
5 | - .
6 | excludePaths:
7 | - Tests
8 | - vendor
9 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | .
11 | vendor/*
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Resources/config/twig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./
6 |
7 |
8 | ./Resources
9 | ./Tests
10 | ./vendor
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ./Tests/
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2014 Michael Roterman
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 |
--------------------------------------------------------------------------------
/Twig/TmdbExtension.php:
--------------------------------------------------------------------------------
1 | load();
22 |
23 | $this->helper = new ImageHelper($configuration);
24 | }
25 |
26 | /**
27 | * @return array
28 | */
29 | public function getFilters(): array
30 | {
31 | return array(
32 | new TwigFilter('tmdb_image_html', array($this, 'getHtml')),
33 | new TwigFilter('tmdb_image_url', array($this, 'getUrl')),
34 | );
35 | }
36 |
37 | public function getHtml(string $image, string $size = 'original', int $width = null, int $height = null): string
38 | {
39 | return $this->helper->getHtml($image, $size, $width, $height);
40 | }
41 |
42 | public function getUrl(string $image, string $size = 'original'): string
43 | {
44 | return $this->helper->getUrl($image, $size);
45 | }
46 |
47 | public function getName(): string
48 | {
49 | return 'tmdb_extension';
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/ClientConfiguration.php:
--------------------------------------------------------------------------------
1 | $options
25 | */
26 | public function __construct(
27 | ApiToken $apiToken,
28 | EventDispatcherInterface $eventDispatcher,
29 | ClientInterface $client,
30 | RequestFactoryInterface $requestFactory,
31 | ResponseFactoryInterface $responseFactory,
32 | StreamFactoryInterface $streamFactory,
33 | UriFactoryInterface $uriFactory,
34 | array $options = []
35 | ) {
36 | $options['api_token'] = $apiToken;
37 | $options['event_dispatcher']['adapter'] = $eventDispatcher;
38 | $options['http']['client'] = $client;
39 | $options['http']['request_factory'] = $requestFactory;
40 | $options['http']['response_factory'] = $responseFactory;
41 | $options['http']['stream_factory'] = $streamFactory;
42 | $options['http']['uri_factory'] = $uriFactory;
43 |
44 | // Library handles it as an api_token
45 | unset($options['bearer_token']);
46 |
47 | parent::__construct($options);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Resources/test/configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": {
3 | "base_url": "http://image.tmdb.org/t/p/",
4 | "secure_base_url": "https://image.tmdb.org/t/p/",
5 | "backdrop_sizes": [
6 | "w300",
7 | "w780",
8 | "w1280",
9 | "original"
10 | ],
11 | "logo_sizes": [
12 | "w45",
13 | "w92",
14 | "w154",
15 | "w185",
16 | "w300",
17 | "w500",
18 | "original"
19 | ],
20 | "poster_sizes": [
21 | "w92",
22 | "w154",
23 | "w185",
24 | "w342",
25 | "w500",
26 | "w780",
27 | "original"
28 | ],
29 | "profile_sizes": [
30 | "w45",
31 | "w185",
32 | "h632",
33 | "original"
34 | ],
35 | "still_sizes": [
36 | "w92",
37 | "w185",
38 | "w300",
39 | "original"
40 | ]
41 | },
42 | "change_keys": [
43 | "adult",
44 | "air_date",
45 | "also_known_as",
46 | "alternative_titles",
47 | "biography",
48 | "birthday",
49 | "budget",
50 | "cast",
51 | "certifications",
52 | "character_names",
53 | "created_by",
54 | "crew",
55 | "deathday",
56 | "episode",
57 | "episode_number",
58 | "episode_run_time",
59 | "freebase_id",
60 | "freebase_mid",
61 | "general",
62 | "genres",
63 | "guest_stars",
64 | "homepage",
65 | "images",
66 | "imdb_id",
67 | "languages",
68 | "name",
69 | "network",
70 | "origin_country",
71 | "original_name",
72 | "original_title",
73 | "overview",
74 | "parts",
75 | "place_of_birth",
76 | "plot_keywords",
77 | "production_code",
78 | "production_companies",
79 | "production_countries",
80 | "releases",
81 | "revenue",
82 | "runtime",
83 | "season",
84 | "season_number",
85 | "season_regular",
86 | "spoken_languages",
87 | "status",
88 | "tagline",
89 | "title",
90 | "translations",
91 | "tvdb_id",
92 | "tvrage_id",
93 | "type",
94 | "video",
95 | "videos"
96 | ]
97 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-tmdb/symfony",
3 | "license": "MIT",
4 | "type": "symfony-bundle",
5 | "description": "Symfony Bundle for TMDB (The Movie Database) API. Provides easy access to the php-tmdb/api library.",
6 | "homepage": "https://github.com/php-tmdb/symfony",
7 | "keywords": ["tmdb", "api", "php","wrapper", "movie", "cinema", "tv", "tv show", "tvdb", "symfony", "symfony4", "symfony5"],
8 | "authors": [
9 | {
10 | "name": "Michael Roterman",
11 | "homepage": "http://wtfz.net",
12 | "email": "michael@wtfz.net"
13 | }
14 | ],
15 | "require": {
16 | "php": "^7.4 || ^8.0",
17 | "php-tmdb/api": "^4.0",
18 | "symfony/config": "^5.4 || ^6.0",
19 | "symfony/dependency-injection": "^5.4 || ^6.0",
20 | "symfony/event-dispatcher": "^5.4 || ^6.0",
21 | "symfony/http-kernel": "^5.4 || ^6.0",
22 | "symfony/yaml": "^5.4 || ^6.0",
23 | "twig/twig": "^3.0"
24 | },
25 | "scripts": {
26 | "test": "vendor/bin/phpunit",
27 | "test-ci": "vendor/bin/phpunit --coverage-text --coverage-clover=build/coverage.xml coverage",
28 | "test-coverage": "php -d xdebug.mode=coverage vendor/bin/phpunit --coverage-html build/coverage",
29 | "test-cs": "vendor/bin/phpcs",
30 | "test-phpstan": "vendor/bin/phpstan analyse -c phpstan.neon . --level 7 --no-progress",
31 | "test-psalm": "vendor/bin/psalm --show-info=true ."
32 | },
33 | "require-dev": {
34 | "nyholm/psr7": "^1.2",
35 | "slevomat/coding-standard": "^6.4.1",
36 | "squizlabs/php_codesniffer": "^3.5.8",
37 | "php-http/guzzle7-adapter": "^0.1",
38 | "phpstan/phpstan": "^1.0",
39 | "phpunit/phpunit": "^7.5 || ^8.0 || ^9.3",
40 | "symfony/framework-bundle": "^5.4 || ^6.0",
41 | "symfony/phpunit-bridge": "^5.4 || ^6.0",
42 | "vimeo/psalm": "^4.0",
43 | "php-http/cache-plugin": "^1.7"
44 | },
45 | "autoload": {
46 | "psr-4": { "Tmdb\\SymfonyBundle\\": "" }
47 | },
48 | "config": {
49 | "allow-plugins": {
50 | "dealerdirect/phpcodesniffer-composer-installer": true,
51 | "php-http/discovery": true
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/TmdbSymfonyBundle.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new ConfigurationPass());
36 | $container->addCompilerPass(new EventDispatchingPass());
37 |
38 | $targets = [
39 | ClientInterface::class => self::PSR18_CLIENTS,
40 | RequestFactoryInterface::class => self::PSR17_REQUEST_FACTORIES,
41 | ResponseFactoryInterface::class => self::PSR17_RESPONSE_FACTORIES,
42 | StreamFactoryInterface::class => self::PSR17_STREAM_FACTORIES,
43 | UriFactoryInterface::class => self::PSR17_URI_FACTORIES,
44 | EventDispatcherInterface::class => self::PSR14_EVENT_DISPATCHERS
45 | ];
46 |
47 | foreach ($targets as $interface => $tag) {
48 | $container
49 | ->registerForAutoconfiguration($interface)
50 | ->addTag($tag)
51 | ;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Resources/config/repositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/Resources/config/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | %tmdb.api_token%
14 |
15 |
16 |
17 | %tmdb.bearer_token%
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
33 |
34 |
35 |
37 |
39 |
41 |
42 |
43 |
44 |
45 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | %tmdb.client.options%
55 |
56 |
57 |
58 |
60 |
61 |
62 |
63 |
64 | %tmdb.client.options%
65 |
66 |
67 |
69 |
70 |
71 |
72 |
73 |
75 |
76 |
77 |
78 |
79 |
80 |
82 |
83 |
84 |
85 |
86 |
87 |
89 |
91 |
93 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/DependencyInjection/CompilerPass/ConfigurationPass.php:
--------------------------------------------------------------------------------
1 | $parameters */
20 | $parameters = $container->getParameter('tmdb.options');
21 | $configDefinition = $container->getDefinition(ClientConfiguration::class);
22 |
23 | // By default, the first argument is always referenced to the ApiToken.
24 | if (null !== $bearerToken = $parameters['options']['bearer_token']) {
25 | $configDefinition->replaceArgument(0, new Reference(BearerToken::class));
26 | }
27 |
28 | $this->setupEventDispatcher($container, $configDefinition, $parameters);
29 | $this->setupHttpClient($container, $configDefinition, $parameters);
30 | }
31 |
32 | /**
33 | * @param array $parameters
34 | */
35 | private function setupEventDispatcher(
36 | ContainerBuilder $container,
37 | Definition $configDefinition,
38 | array $parameters
39 | ): void {
40 | if (!$container->hasDefinition($parameters['options']['event_dispatcher']['adapter'])) {
41 | $this->tryToAliasAutowiredInterfacesIfPossible(
42 | $container,
43 | $parameters['options']['event_dispatcher']['adapter'],
44 | TmdbSymfonyBundle::PSR14_EVENT_DISPATCHERS,
45 | 'tmdb_symfony.options.event_dispatcher.adapter'
46 | );
47 | }
48 |
49 | $configDefinition->replaceArgument(1, new Reference($parameters['options']['event_dispatcher']['adapter']));
50 | }
51 |
52 | /**
53 | * @param array $parameters
54 | */
55 | private function setupHttpClient(
56 | ContainerBuilder $container,
57 | Definition $configDefinition,
58 | array $parameters
59 | ): void {
60 | if (!$container->hasDefinition($parameters['options']['http']['client'])) {
61 | $this->tryToAliasAutowiredInterfacesIfPossible(
62 | $container,
63 | $parameters['options']['http']['client'],
64 | TmdbSymfonyBundle::PSR18_CLIENTS,
65 | 'tmdb_symfony.options.http.client'
66 | );
67 | }
68 |
69 | if (!$container->hasDefinition($parameters['options']['http']['request_factory'])) {
70 | $this->tryToAliasAutowiredInterfacesIfPossible(
71 | $container,
72 | $parameters['options']['http']['request_factory'],
73 | TmdbSymfonyBundle::PSR17_REQUEST_FACTORIES,
74 | 'tmdb_symfony.options.http.request_factory'
75 | );
76 | }
77 |
78 | if (!$container->hasDefinition($parameters['options']['http']['response_factory'])) {
79 | $this->tryToAliasAutowiredInterfacesIfPossible(
80 | $container,
81 | $parameters['options']['http']['response_factory'],
82 | TmdbSymfonyBundle::PSR17_RESPONSE_FACTORIES,
83 | 'tmdb_symfony.options.http.response_factory'
84 | );
85 | }
86 |
87 | if (!$container->hasDefinition($parameters['options']['http']['stream_factory'])) {
88 | $this->tryToAliasAutowiredInterfacesIfPossible(
89 | $container,
90 | $parameters['options']['http']['stream_factory'],
91 | TmdbSymfonyBundle::PSR17_STREAM_FACTORIES,
92 | 'tmdb_symfony.options.http.stream_factory'
93 | );
94 | }
95 |
96 | if (!$container->hasDefinition($parameters['options']['http']['uri_factory'])) {
97 | $this->tryToAliasAutowiredInterfacesIfPossible(
98 | $container,
99 | $parameters['options']['http']['uri_factory'],
100 | TmdbSymfonyBundle::PSR17_URI_FACTORIES,
101 | 'tmdb_symfony.options.http.uri_factory'
102 | );
103 | }
104 |
105 | $configDefinition->replaceArgument(2, new Reference($parameters['options']['http']['client']));
106 | $configDefinition->replaceArgument(3, new Reference($parameters['options']['http']['request_factory']));
107 | $configDefinition->replaceArgument(4, new Reference($parameters['options']['http']['response_factory']));
108 | $configDefinition->replaceArgument(5, new Reference($parameters['options']['http']['stream_factory']));
109 | $configDefinition->replaceArgument(6, new Reference($parameters['options']['http']['uri_factory']));
110 | }
111 |
112 | /**
113 | * @throws \RuntimeException
114 | */
115 | protected function tryToAliasAutowiredInterfacesIfPossible(
116 | ContainerBuilder $container,
117 | string $alias,
118 | string $tag,
119 | string $configurationPath
120 | ): void {
121 | $services = $container->findTaggedServiceIds($tag);
122 |
123 | if (!empty($services)) {
124 | if (count($services) > 1) {
125 | throw new RuntimeException(
126 | sprintf(
127 | 'Trying to automatically configure tmdb symfony bundle, however we found %d applicable services'
128 | . ' ( %s ) for tag "%s", please set one of these explicitly in your configuration under "%s".',
129 | count($services),
130 | implode(', ', array_keys($services)),
131 | $tag,
132 | $configurationPath
133 | )
134 | );
135 | }
136 |
137 | $serviceIds = array_keys($services);
138 | $serviceId = array_shift($serviceIds);
139 |
140 | $container->setAlias($alias, $serviceId);
141 | return;
142 | }
143 |
144 | throw new RuntimeException(
145 | sprintf(
146 | 'Unable to find any services tagged with "%s", ' .
147 | 'please set it in the configuration explicitly under "%s".',
148 | $tag,
149 | $configurationPath
150 | )
151 | );
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/DependencyInjection/TmdbSymfonyExtension.php:
--------------------------------------------------------------------------------
1 | $configs
45 | */
46 | public function load(array $configs, ContainerBuilder $container): void
47 | {
48 | $configuration = new Configuration();
49 | $config = $this->processConfiguration($configuration, $configs);
50 |
51 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
52 | $loader->load('services.xml');
53 |
54 | $container->setParameter('tmdb.api_token', $config['options']['api_token']);
55 | $container->setParameter('tmdb.bearer_token', $config['options']['bearer_token']);
56 |
57 | if (!$config['disable_legacy_aliases']) {
58 | $this->handleLegacyGeneralAliases($container);
59 | }
60 |
61 | if ($config['repositories']['enabled']) {
62 | $loader->load('repositories.xml');
63 |
64 | if (!$config['disable_legacy_aliases']) {
65 | $this->handleLegacyRepositoryAliases($container);
66 | }
67 | }
68 |
69 | if ($config['twig_extension']['enabled']) {
70 | $loader->load('twig.xml');
71 |
72 | if (!$config['disable_legacy_aliases']) {
73 | $this->handleLegacyTwigExtensionAlias($container);
74 | }
75 | }
76 |
77 | $container->setParameter('tmdb.options', $config);
78 | $container->setParameter('tmdb.client.options', $config['options']);
79 | }
80 |
81 | /**
82 | * Alias mapping for legacy constructs; public to abuse within test suite.
83 | *
84 | * @return array
85 | */
86 | public function getLegacyAliasMapping(): array
87 | {
88 | return [
89 | 'repositories' => [
90 | 'tmdb.authentication_repository' => AuthenticationRepository::class,
91 | 'tmdb.account_repository' => AccountRepository::class,
92 | 'tmdb.certification_repository' => CertificationRepository::class,
93 | 'tmdb.changes_repository' => ChangesRepository::class,
94 | 'tmdb.collection_repository' => CollectionRepository::class,
95 | 'tmdb.company_repository' => CompanyRepository::class,
96 | 'tmdb.configuration_repository' => ConfigurationRepository::class,
97 | 'tmdb.credits_repository' => CreditsRepository::class,
98 | 'tmdb.discover_repository' => DiscoverRepository::class,
99 | 'tmdb.find_repository' => FindRepository::class,
100 | 'tmdb.genre_repository' => GenreRepository::class,
101 | 'tmdb.jobs_repository' => JobsRepository::class,
102 | 'tmdb.keyword_repository' => KeywordRepository::class,
103 | 'tmdb.list_repository' => ListRepository::class,
104 | 'tmdb.movie_repository' => MovieRepository::class,
105 | 'tmdb.network_repository' => NetworkRepository::class,
106 | 'tmdb.people_repository' => PeopleRepository::class,
107 | 'tmdb.review_repository' => ReviewRepository::class,
108 | 'tmdb.search_repository' => SearchRepository::class,
109 | 'tmdb.tv_repository' => TvRepository::class,
110 | 'tmdb.tv_episode_repository' => TvEpisodeRepository::class,
111 | 'tmdb.tv_season_repository' => TvSeasonRepository::class,
112 | ],
113 | 'general' => [
114 | 'tmdb.client' => Client::class,
115 | 'tmdb.api_token' => ApiToken::class,
116 | 'tmdb.configuration' => ClientConfiguration::class
117 | ],
118 | 'twig' => [
119 | 'tmdb.twig.image_extension' => TmdbExtension::class
120 | ]
121 | ];
122 | }
123 |
124 | /**
125 | * Performs mapping of legacy aliases to their new service identifiers.
126 | *
127 | * @todo major release remove alias mapping of legacy muck :-)
128 | *
129 | * @param array $mapping
130 | */
131 | protected function performAliasMapping(ContainerBuilder $container, array $mapping = []): void
132 | {
133 | foreach ($mapping as $legacyAlias => $newAlias) {
134 | // @todo fix alias with public/private properties
135 | $container
136 | ->setAlias($legacyAlias, new Alias($newAlias))
137 | ;
138 | }
139 | }
140 |
141 | /**
142 | * Handle general lgeacy aliases.
143 | */
144 | protected function handleLegacyGeneralAliases(ContainerBuilder $container): void
145 | {
146 | $mapping = $this->getLegacyAliasMapping();
147 | $this->performAliasMapping($container, $mapping['general']);
148 | }
149 |
150 | /**
151 | * Map repository legacy aliases
152 | */
153 | protected function handleLegacyRepositoryAliases(ContainerBuilder $container): void
154 | {
155 | $mapping = $this->getLegacyAliasMapping();
156 | $this->performAliasMapping($container, $mapping['repositories']);
157 | }
158 |
159 | /**
160 | * Map twig legacy aliases
161 | */
162 | protected function handleLegacyTwigExtensionAlias(ContainerBuilder $container): void
163 | {
164 | $mapping = $this->getLegacyAliasMapping();
165 | $this->performAliasMapping($container, $mapping['twig']);
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/DependencyInjection/CompilerPass/EventDispatchingPass.php:
--------------------------------------------------------------------------------
1 | $parameters */
36 | $parameters = $container->getParameter('tmdb.options');
37 | $clientOptions = $parameters['options'];
38 |
39 | if ($container->hasAlias($clientOptions['event_dispatcher']['adapter'])) {
40 | $definition = $container->getDefinition(
41 | $container->getAlias($clientOptions['event_dispatcher']['adapter'])
42 | );
43 | } else {
44 | $definition = $container->getDefinition($clientOptions['event_dispatcher']['adapter']);
45 | }
46 |
47 | if ($definition->getClass() === EventDispatcher::class) {
48 | $this->handleSymfonyEventDispatcherRegistration($container, $definition, $parameters);
49 | }
50 | }
51 |
52 | /**
53 | * @param array $parameters
54 | */
55 | private function handleSymfonyEventDispatcherRegistration(
56 | ContainerBuilder $container,
57 | Definition $eventDispatcher,
58 | array $parameters
59 | ): void {
60 | $cacheEnabled = $parameters['cache']['enabled'];
61 | $logEnabled = $parameters['log']['enabled'];
62 |
63 | $requestListener = $cacheEnabled ?
64 | $this->getPsr6CacheRequestListener($container, $parameters) :
65 | $this->getRequestListener($container, $parameters);
66 |
67 | if ($logEnabled) {
68 | $this->handleLoggerListeners($container, $eventDispatcher, $parameters);
69 | }
70 |
71 | $this->registerEventListener(
72 | $eventDispatcher,
73 | RequestEvent::class,
74 | $requestListener->getClass()
75 | );
76 |
77 | if (null !== $bearerToken = $parameters['options']['bearer_token']) {
78 | $definition = $container->getDefinition(ApiTokenRequestListener::class);
79 | $definition->replaceArgument(0, new Reference(BearerToken::class));
80 | }
81 |
82 | $this->registerEventListener(
83 | $eventDispatcher,
84 | BeforeRequestEvent::class,
85 | ApiTokenRequestListener::class
86 | );
87 |
88 | $this->registerEventListener(
89 | $eventDispatcher,
90 | BeforeRequestEvent::class,
91 | ContentTypeJsonRequestListener::class
92 | );
93 |
94 | $this->registerEventListener(
95 | $eventDispatcher,
96 | BeforeRequestEvent::class,
97 | AcceptJsonRequestListener::class
98 | );
99 |
100 | $definition = $container->getDefinition(UserAgentRequestListener::class);
101 | $definition->replaceArgument(
102 | 0,
103 | sprintf(
104 | 'php-tmdb/symfony/%s php-tmdb/api/%s',
105 | TmdbSymfonyBundle::VERSION,
106 | Client::VERSION
107 | )
108 | );
109 |
110 | $this->registerEventListener(
111 | $eventDispatcher,
112 | BeforeRequestEvent::class,
113 | UserAgentRequestListener::class
114 | );
115 | }
116 |
117 | /**
118 | * @param array $parameters
119 | */
120 | private function getRequestListener(
121 | ContainerBuilder $container,
122 | array $parameters
123 | ): Definition {
124 | return $container->getDefinition(RequestListener::class)
125 | ->replaceArgument(
126 | 1,
127 | new Reference($parameters['options']['event_dispatcher']['adapter'])
128 | );
129 | }
130 |
131 | /**
132 | * @param array $parameters
133 | */
134 | private function getPsr6CacheRequestListener(
135 | ContainerBuilder $container,
136 | array $parameters
137 | ): Definition {
138 | return $container->getDefinition(Psr6CachedRequestListener::class)
139 | ->replaceArgument(1, new Reference($parameters['options']['event_dispatcher']['adapter']))
140 | ->replaceArgument(2, new Reference($parameters['cache']['adapter']))
141 | ->replaceArgument(3, new Reference($parameters['options']['http']['stream_factory']));
142 | }
143 |
144 | /**
145 | * @param array $parameters
146 | */
147 | private function handleLogging(
148 | string $event,
149 | string $listener,
150 | Definition $eventDispatcher,
151 | ContainerBuilder $container,
152 | array $parameters
153 | ): void {
154 | $options = $parameters[$listener];
155 | $configEntry = sprintf('tmdb_symfony.log.%s', $listener);
156 |
157 | if (!$options['enabled']) {
158 | return;
159 | }
160 |
161 | if (!$options['adapter']) {
162 | $options['adapter'] = $parameters['adapter'];
163 | }
164 |
165 | if (!$container->hasDefinition($options['adapter']) && !$container->hasAlias($options['adapter'])) {
166 | throw new \RuntimeException(sprintf(
167 | 'Unable to find a definition for the adapter to provide tmdb request logging, you gave "%s" for "%s".',
168 | $options['adapter'],
169 | sprintf('%s.%s', $configEntry, 'adapter')
170 | ));
171 | }
172 |
173 | if (!$container->hasDefinition($options['listener']) && !$container->hasAlias($options['listener'])) {
174 | throw new \RuntimeException(sprintf(
175 | 'Unable to find a definition for the listener to provide tmdb request logging, you gave "%s" for "%s".',
176 | $options['listener'],
177 | sprintf('%s.%s', $configEntry, 'listener')
178 | ));
179 | }
180 |
181 | if (!$container->hasDefinition($options['formatter']) && !$container->hasAlias($options['formatter'])) {
182 | throw new \RuntimeException(sprintf(
183 | 'Unable to find a definition for the formatter to provide tmdb request logging, ' .
184 | 'you gave "%s" for "%s".',
185 | $options['formatter'],
186 | sprintf('%s.%s', $configEntry, 'formatter')
187 | ));
188 | }
189 |
190 | $adapter = $container->hasAlias($options['adapter']) ?
191 | $container->getAlias($options['adapter']) :
192 | $options['adapter'];
193 |
194 | $listenerDefinition = $container->getDefinition($options['listener']);
195 | $listenerDefinition->replaceArgument(0, new Reference($adapter));
196 | $listenerDefinition->replaceArgument(1, new Reference($options['formatter']));
197 |
198 | // Cannot assume if this was replaced this parameter will be kept.
199 | if ($listenerDefinition->getClass() === LogHydrationListener::class) {
200 | $listenerDefinition->replaceArgument(
201 | 2,
202 | $parameters['hydration']['with_hydration_data']
203 | );
204 | }
205 |
206 | $this->registerEventListener(
207 | $eventDispatcher,
208 | $event,
209 | $listenerDefinition->getClass()
210 | );
211 | }
212 |
213 | /**
214 | * Register listeners for logging.
215 | *
216 | * @param array $parameters
217 | */
218 | private function handleLoggerListeners(ContainerBuilder $container, Definition $eventDispatcher, array $parameters): void
219 | {
220 | $listeners = [
221 | BeforeRequestEvent::class => 'request_logging',
222 | ResponseEvent::class => 'response_logging',
223 | HttpClientExceptionEvent::class => 'client_exception_logging',
224 | TmdbExceptionEvent::class => 'api_exception_logging',
225 | BeforeHydrationEvent::class => 'hydration'
226 | ];
227 |
228 | foreach ($listeners as $event => $listener) {
229 | $this->handleLogging(
230 | $event,
231 | $listener,
232 | $eventDispatcher,
233 | $container,
234 | $parameters['log']
235 | );
236 | }
237 | }
238 |
239 | private function registerEventListener(
240 | Definition $eventDispatcher,
241 | string $event,
242 | string $reference
243 | ): void {
244 | $eventDispatcher->addMethodCall(
245 | 'addListener',
246 | [
247 | $event,
248 | new Reference($reference)
249 | ]
250 | );
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
34 |
35 | $this->addRootChildren($rootNode);
36 | $this->addOptionsSection($rootNode);
37 | $this->addLogSection($rootNode);
38 | $this->addCacheSection($rootNode);
39 |
40 | return $treeBuilder;
41 | }
42 |
43 | private function addRootChildren(ArrayNodeDefinition $rootNode): void
44 | {
45 | $rootNode
46 | ->beforeNormalization()
47 | ->ifTrue(function ($v) {
48 | return isset($v['api_key']) && !empty($v['api_key']);
49 | })
50 | ->then(function ($v) {
51 | $v['options']['api_token'] = $v['api_key'];
52 |
53 | return $v;
54 | })
55 | ->end()
56 | ->addDefaultsIfNotSet()
57 | ->children()
58 | ->scalarNode('api_key')->isRequired()->cannotBeEmpty()->end()
59 | ->scalarNode('session_token')->defaultValue(null)->end()
60 | ->arrayNode('repositories')->canBeDisabled()->end()
61 | ->arrayNode('twig_extension')->canBeDisabled()->end()
62 | ->booleanNode('disable_legacy_aliases')->defaultFalse()->end()
63 | ->end()
64 | ;
65 | }
66 |
67 | private function addOptionsSection(ArrayNodeDefinition $rootNode): void
68 | {
69 | $rootNode
70 | ->children()
71 | ->arrayNode('options')
72 | ->addDefaultsIfNotSet()
73 | ->children()
74 | ->scalarNode('api_token')
75 | ->defaultValue(null)
76 | ->info('Will be set by root api_key')
77 | ->end()
78 | ->scalarNode('bearer_token')
79 | ->defaultValue(null)
80 | ->info('If set will be used instead of api token')
81 | ->end()
82 | ->scalarNode('secure')->defaultTrue()->end()
83 | ->scalarNode('host')->defaultValue(Client::TMDB_URI)->end()
84 | ->scalarNode('guest_session_token')->defaultValue(null)->end()
85 | ->arrayNode('event_dispatcher')
86 | ->info('Reference to a service which implements PSR-14 Event Dispatcher')
87 | ->addDefaultsIfNotSet()
88 | ->children()
89 | ->scalarNode('adapter')
90 | ->isRequired()->cannotBeEmpty()
91 | ->defaultValue(EventDispatcherInterface::class)
92 | ->end()
93 | ->end()
94 | ->end()
95 | ->arrayNode('http')
96 | ->addDefaultsIfNotSet()
97 | ->children()
98 | ->scalarNode('client')
99 | ->defaultValue(ClientInterface::class)
100 | ->info('Reference to a service which implements PSR-18 HTTP Client')
101 | ->end()
102 | ->scalarNode('request_factory')
103 | ->defaultValue(RequestFactoryInterface::class)
104 | ->info('Reference to a service which implements PSR-17 HTTP Factories')
105 | ->end()
106 | ->scalarNode('response_factory')
107 | ->defaultValue(ResponseFactoryInterface::class)
108 | ->info('Reference to a service which implements PSR-17 HTTP Factories')
109 | ->end()
110 | ->scalarNode('stream_factory')
111 | ->defaultValue(StreamFactoryInterface::class)
112 | ->info('Reference to a service which implements PSR-17 HTTP Factories')
113 | ->end()
114 | ->scalarNode('uri_factory')
115 | ->defaultValue(UriFactoryInterface::class)
116 | ->info('Reference to a service which implements PSR-17 HTTP Factories')
117 | ->end()
118 | ->end()
119 | ->end()
120 | ->arrayNode('hydration')
121 | ->addDefaultsIfNotSet()
122 | ->children()
123 | ->booleanNode('event_listener_handles_hydration')->defaultFalse()->end()
124 | ->arrayNode('only_for_specified_models')
125 | ->scalarPrototype()->end()
126 | ->end()
127 | ->end()
128 | ->end()
129 | ->end()
130 | ->end()
131 | ->end()
132 | ;
133 | }
134 |
135 | private function addLogSection(ArrayNodeDefinition $rootNode): void
136 | {
137 | $rootNode
138 | ->children()
139 | ->arrayNode('log')
140 | ->addDefaultsIfNotSet()
141 | ->canBeEnabled()
142 | ->children()
143 | ->scalarNode('adapter')
144 | ->defaultValue(LoggerInterface::class)
145 | ->info('When registering a channel in monolog as "tmdb" for example, monolog.logger.tmdb')
146 | ->end()
147 | ->arrayNode('request_logging')
148 | ->addDefaultsIfNotSet()
149 | ->children()
150 | ->scalarNode('enabled')->defaultValue('%kernel.debug%')->end()
151 | ->scalarNode('listener')->defaultValue(LogHttpMessageListener::class)->end()
152 | ->scalarNode('adapter')->defaultValue(null)->end()
153 | ->scalarNode('formatter')->defaultValue(SimpleHttpMessageFormatter::class)->end()
154 | ->end()
155 | ->end()
156 | ->arrayNode('response_logging')
157 | ->addDefaultsIfNotSet()
158 | ->children()
159 | ->scalarNode('enabled')->defaultValue('%kernel.debug%')->end()
160 | ->scalarNode('listener')->defaultValue(LogHttpMessageListener::class)->end()
161 | ->scalarNode('adapter')->defaultValue(null)->end()
162 | ->scalarNode('formatter')->defaultValue(SimpleHttpMessageFormatter::class)->end()
163 | ->end()
164 | ->end()
165 | ->arrayNode('api_exception_logging')
166 | ->addDefaultsIfNotSet()
167 | ->children()
168 | ->scalarNode('enabled')->defaultValue('%kernel.debug%')->end()
169 | ->scalarNode('listener')->defaultValue(LogApiErrorListener::class)->end()
170 | ->scalarNode('adapter')->defaultValue(null)->end()
171 | ->scalarNode('formatter')->defaultValue(SimpleTmdbApiExceptionFormatter::class)->end()
172 | ->end()
173 | ->end()
174 | ->arrayNode('client_exception_logging')
175 | ->addDefaultsIfNotSet()
176 | ->children()
177 | ->scalarNode('enabled')->defaultValue('%kernel.debug%')->end()
178 | ->scalarNode('listener')->defaultValue(LogHttpMessageListener::class)->end()
179 | ->scalarNode('adapter')->defaultValue(null)->end()
180 | ->scalarNode('formatter')->defaultValue(SimpleHttpMessageFormatter::class)->end()
181 | ->end()
182 | ->end()
183 | ->arrayNode('hydration')
184 | ->addDefaultsIfNotSet()
185 | ->children()
186 | ->scalarNode('enabled')->defaultValue('%kernel.debug%')->end()
187 | ->scalarNode('listener')->defaultValue(LogHydrationListener::class)->end()
188 | ->scalarNode('adapter')->defaultValue(null)->end()
189 | ->scalarNode('formatter')->defaultValue(SimpleHydrationFormatter::class)->end()
190 | ->booleanNode('with_hydration_data')->defaultFalse()->end()
191 | ->end()
192 | ->end()
193 | ->end()
194 | ->end()
195 | ->end()
196 | ;
197 | }
198 |
199 | private function addCacheSection(ArrayNodeDefinition $rootNode): void
200 | {
201 | $rootNode
202 | ->children()
203 | ->arrayNode('cache')
204 | ->addDefaultsIfNotSet()
205 | ->canBeEnabled()
206 | ->children()
207 | ->scalarNode('adapter')->defaultValue('Psr\Cache\CacheItemPoolInterface')->end()
208 | ->end()
209 | ->end()
210 | ->end()
211 | ;
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Symfony Bundle for use together with the [php-tmdb/api](https://github.com/php-tmdb/api) TMDB API Wrapper.
2 |
3 | [](https://packagist.org/packages/php-tmdb/symfony)
4 | [](https://github.com/php-tmdb/symfony/releases)
5 | [](https://github.com/php-tmdb/symfony/actions?query=workflow%3A%22Continuous+Integration%22)
6 | [](https://github.com/php-tmdb/symfony/actions?query=workflow%3A%22Coding+Standards%22)
7 | [](https://codecov.io/gh/php-tmdb/symfony)
8 | [](https://packagist.org/packages/php-tmdb/api)
9 | [](https://packagist.org/packages/php-tmdb/symfony)
10 |
11 | Compatible with Symfony 5 and 6, PHP 7.4 and up.
12 |
13 | ## Buy me a coffee, or a beer :-)
14 |
15 |
16 |
17 | My stomach will appreciate your donation!
18 |
19 | Installation
20 | ------------
21 |
22 | - [Install Composer](https://getcomposer.org/doc/00-intro.md)
23 | - [Install php-tmdb/api dependencies](https://github.com/php-tmdb/api#installation)
24 | - For development within Symfony we recommend making use of Symfony's PSR-18 HTTP Client _`Symfony\Component\HttpClient\Psr18Client`_,
25 | as when non-cached results pass your profiler will be filled with data.
26 |
27 | Then require the bundle:
28 |
29 | ```
30 | composer require php-tmdb/symfony:^4
31 | ```
32 |
33 | Configuration
34 | ----------------
35 | Register the bundle in `app/bundles.php`:
36 |
37 | ```php
38 | ['all' => true],
43 | ];
44 | ```
45 |
46 | Add to your `app/config/config.yml` the following, or replace values with services of your choice ( PSR-18 Http Client / PSR-17 Factories ):
47 |
48 | ```yaml
49 | tmdb_symfony:
50 | api_key: YOUR_API_KEY_HERE
51 | options:
52 | http:
53 | client: Symfony\Component\HttpClient\Psr18Client
54 | request_factory: Nyholm\Psr7\Factory\Psr17Factory
55 | response_factory: Nyholm\Psr7\Factory\Psr17Factory
56 | stream_factory: Nyholm\Psr7\Factory\Psr17Factory
57 | uri_factory: Nyholm\Psr7\Factory\Psr17Factory
58 | ```
59 |
60 | `services.yaml`:
61 |
62 | ```yaml
63 | services:
64 | Symfony\Component\HttpClient\Psr18Client:
65 | class: Symfony\Component\HttpClient\Psr18Client
66 |
67 | Nyholm\Psr7\Factory\Psr17Factory:
68 | class: Nyholm\Psr7\Factory\Psr17Factory
69 | ```
70 |
71 | __Configure caching__
72 |
73 | You can use any PSR-6 cache you wish to use, we will simply use symfony's cache.
74 |
75 | When making use of caching, make sure to also include `php-http/cache-plugin` in composer, this plugin handles the logic for us,
76 | so we don't have to re-invent the wheel.
77 |
78 | You are however also free to choose to implement your own cache listener, or add the caching logic inside the http client of your choice.
79 |
80 | ```shell script
81 | composer require php-http/cache-plugin:^1.7
82 | ```
83 |
84 | First off configure the cache pool in symfony `config/cache.yaml`:
85 |
86 | ```yaml
87 | framework:
88 | cache:
89 | pools:
90 | cache.tmdb:
91 | adapter: cache.adapter.filesystem
92 | default_lifetime: 86400
93 | ```
94 |
95 | Then in your `tmdb_symfony.yaml` configuration enable the cache and reference this cache pool:
96 |
97 | ```yaml
98 | tmdb_symfony:
99 | api_key: YOUR_API_KEY_HERE
100 | cache:
101 | enabled: true
102 | adapter: cache.tmdb
103 | ```
104 |
105 | __Want to make use of logging?__
106 |
107 | Logging capabilities as of `4.0` allow you to make a fine-grained configuration.
108 |
109 | You can use any PSR-3 logger you wish to use, we will simply use monolog.
110 |
111 | First off configure the monolog and add a channel and handler:
112 |
113 | ```yaml
114 | monolog:
115 | channels:
116 | - tmdb
117 | handlers:
118 | tmdb:
119 | type: stream
120 | path: "%kernel.logs_dir%/php-tmdb--symfony.%kernel.environment%.log"
121 | level: info
122 | channels: ["tmdb"]
123 | ```
124 |
125 | Then in your `tmdb_symfony.yaml` configuration:
126 |
127 | ```yaml
128 | tmdb_symfony:
129 | api_key: YOUR_API_KEY_HERE
130 | log:
131 | enabled: true
132 | adapter: monolog.logger.tmdb
133 | hydration:
134 | enabled: true
135 | with_hydration_data: false # We would only recommend to enable this with an in-memory logger, so you have access to the hydration data within the profiler.
136 | adapter: null # you can set different adapters for different logs, leave null to use the main adapter.
137 | listener: Tmdb\Event\Listener\Logger\LogHydrationListener
138 | formatter: Tmdb\Formatter\Hydration\SimpleHydrationFormatter
139 | request_logging:
140 | enabled: true
141 | adapter: null # you can set different adapters for different logs, leave null to use the main adapter.
142 | listener: Tmdb\Event\Listener\Logger\LogHttpMessageListener
143 | formatter: Tmdb\Formatter\HttpMessage\SimpleHttpMessageFormatter
144 | response_logging:
145 | enabled: true
146 | adapter: null # you can set different adapters for different logs, leave null to use the main adapter.
147 | listener: Tmdb\Event\Listener\Logger\LogHttpMessageListener
148 | formatter: Tmdb\Formatter\HttpMessage\SimpleHttpMessageFormatter
149 | api_exception_logging:
150 | enabled: true
151 | adapter: null # you can set different adapters for different logs, leave null to use the main adapter.
152 | listener: Tmdb\Event\Listener\Logger\LogApiErrorListener
153 | formatter: Tmdb\Formatter\TmdbApiException\SimpleTmdbApiExceptionFormatter
154 | client_exception_logging:
155 | enabled: true
156 | adapter: null # you can set different adapters for different logs, leave null to use the main adapter.
157 | listener: Tmdb\Event\Listener\Logger\LogHttpMessageListener
158 | formatter: Tmdb\Formatter\HttpMessage\SimpleHttpMessageFormatter
159 | ```
160 |
161 | __Disable repositories :__
162 |
163 | ```yaml
164 | tmdb_symfony:
165 | api_key: YOUR_API_KEY_HERE
166 | repositories:
167 | enabled: false
168 | ```
169 |
170 | __Disable twig extension :__
171 |
172 | ```yaml
173 | tmdb_symfony:
174 | api_key: YOUR_API_KEY_HERE
175 | twig_extension:
176 | enabled: false
177 | ```
178 | __Disable https :__
179 |
180 | ```yaml
181 | tmdb_symfony:
182 | api_key: YOUR_API_KEY_HERE
183 | options:
184 | secure:
185 | enabled: false
186 | ```
187 |
188 | __Disable legacy aliases :__
189 |
190 | _Set to true to remove all legacy alises ( e.g. `tmdb.client` or `tmdb.movie_repository` )._
191 |
192 | ```yaml
193 | tmdb_symfony:
194 | api_key: YOUR_API_KEY_HERE
195 | disable_legacy_aliases: true
196 | ```
197 |
198 | __Full configuration with defaults :__
199 | ```yaml
200 | tmdb_symfony:
201 | api_key: YOUR_API_KEY_HERE
202 | cache:
203 | enabled: true
204 | adapter: cache.tmdb
205 | log:
206 | enabled: true
207 | adapter: monolog.logger.tmdb
208 | hydration:
209 | enabled: true
210 | with_hydration_data: false
211 | adapter: null
212 | listener: Tmdb\Event\Listener\Logger\LogHydrationListener
213 | formatter: Tmdb\Formatter\Hydration\SimpleHydrationFormatter
214 | request_logging:
215 | enabled: true
216 | adapter: null
217 | listener: Tmdb\Event\Listener\Logger\LogHttpMessageListener
218 | formatter: Tmdb\Formatter\HttpMessage\SimpleHttpMessageFormatter
219 | response_logging:
220 | enabled: true
221 | adapter: null
222 | listener: Tmdb\Event\Listener\Logger\LogHttpMessageListener
223 | formatter: Tmdb\Formatter\HttpMessage\SimpleHttpMessageFormatter
224 | api_exception_logging:
225 | enabled: true
226 | adapter: null
227 | listener: Tmdb\Event\Listener\Logger\LogApiErrorListener
228 | formatter: Tmdb\Formatter\TmdbApiException\SimpleTmdbApiExceptionFormatter
229 | client_exception_logging:
230 | enabled: true
231 | adapter: null
232 | listener: Tmdb\Event\Listener\Logger\LogHttpMessageListener
233 | formatter: Tmdb\Formatter\HttpMessage\SimpleHttpMessageFormatter
234 | options:
235 | bearer_token: YOUR_BEARER_TOKEN_HERE
236 | http:
237 | client: Symfony\Component\HttpClient\Psr18Client
238 | request_factory: Nyholm\Psr7\Factory\Psr17Factory
239 | response_factory: Nyholm\Psr7\Factory\Psr17Factory
240 | stream_factory: Nyholm\Psr7\Factory\Psr17Factory
241 | uri_factory: Nyholm\Psr7\Factory\Psr17Factory
242 | secure: true
243 | host: api.themoviedb.org/3
244 | guest_session_token: null
245 | event_dispatcher:
246 | adapter: event_dispatcher
247 | hydration:
248 | event_listener_handles_hydration: false
249 | only_for_specified_models: { }
250 | api_token: YOUR_API_KEY_HERE # you don't have to set this if you set it at the root level
251 | session_token: null
252 | repositories:
253 | enabled: true
254 | twig_extension:
255 | enabled: true
256 | disable_legacy_aliases: false
257 | ```
258 |
259 | Usage
260 | ----------------
261 |
262 | Obtaining the client
263 |
264 | ```php
265 | client = $client;
279 | }
280 | }
281 | ```
282 |
283 | Obtaining repositories
284 |
285 | ```php
286 | movieRepository = $movieRepository;
301 | }
302 |
303 | public function findMovie(string $id): AbstractModel
304 | {
305 | // Use the auto-wired repository in any of your methods
306 | return $this->movieRepository->load($id);
307 | }
308 | }
309 | ```
310 |
311 | An overview of all the repositories can be found in the services configuration [repositories.xml](https://github.com/php-tmdb/symfony/blob/master/Resources/config/repositories.xml).
312 |
313 | There is also a Twig helper that makes use of the `Tmdb\Helper\ImageHelper` to output urls and html.
314 |
315 | ```twig
316 | {{ movie.backdropImage|tmdb_image_url }}
317 |
318 | {{ movie.backdropImage|tmdb_image_html('original', null, 50)|raw }}
319 | ```
320 |
321 | **For all all other interactions take a look at [php-tmdb/api](https://github.com/php-tmdb/api).**
322 |
--------------------------------------------------------------------------------