├── ClientConfiguration.php ├── DependencyInjection ├── CompilerPass │ ├── ConfigurationPass.php │ └── EventDispatchingPass.php ├── Configuration.php └── TmdbSymfonyExtension.php ├── LICENSE ├── README.md ├── Resources ├── config │ ├── repositories.xml │ ├── services.xml │ └── twig.xml └── test │ └── configuration.json ├── TmdbSymfonyBundle.php ├── Twig └── TmdbExtension.php ├── composer.json ├── phpcs.xml.dist ├── phpstan.neon ├── phpunit.xml.dist └── psalm.xml /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | [![License](https://poser.pugx.org/php-tmdb/symfony/license.png)](https://packagist.org/packages/php-tmdb/symfony) 4 | [![License](https://img.shields.io/github/v/tag/php-tmdb/symfony)](https://github.com/php-tmdb/symfony/releases) 5 | [![Build Status](https://img.shields.io/github/workflow/status/php-tmdb/symfony/Continuous%20Integration?label=phpunit)](https://github.com/php-tmdb/symfony/actions?query=workflow%3A%22Continuous+Integration%22) 6 | [![Build Status](https://img.shields.io/github/workflow/status/php-tmdb/symfony/Coding%20Standards?label=phpcs)](https://github.com/php-tmdb/symfony/actions?query=workflow%3A%22Coding+Standards%22) 7 | [![codecov](https://img.shields.io/codecov/c/github/php-tmdb/symfony?token=gTM9AiO5vH)](https://codecov.io/gh/php-tmdb/symfony) 8 | [![PHP](https://img.shields.io/badge/php->=7.3,%20>=7.4,%20>=8.0-8892BF.svg)](https://packagist.org/packages/php-tmdb/api) 9 | [![Total Downloads](https://poser.pugx.org/php-tmdb/symfony/downloads.svg)](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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Resources/config/twig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | . 11 | vendor/* 12 | 13 | 14 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 2 3 | inferPrivatePropertyTypeFromConstructor: true 4 | paths: 5 | - . 6 | excludePaths: 7 | - Tests 8 | - vendor 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --------------------------------------------------------------------------------