feat : ajout de l'auth et du guichet pour la récupération d'info bovin

This commit is contained in:
2026-01-23 10:03:14 +01:00
commit b279f1ac47
25 changed files with 4565 additions and 0 deletions

31
.gitignore vendored Normal file
View File

@@ -0,0 +1,31 @@
###> symfony/framework-bundle ###
/.env.local
/.env.prod
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
/LOG/
/config/jwt/*.pem
###< symfony/framework-bundle ###
###> friendsofphp/php-cs-fixer ###
/.php-cs-fixer.php
/.php-cs-fixer.cache
###< friendsofphp/php-cs-fixer ###
###> phpunit/phpunit ###
/phpunit.xml
/.phpunit.cache/
###< phpunit/phpunit ###
###> docker ###
docker/.env.docker.local
###< docker ###
###> lexik/jwt-authentication-bundle ###
/config/jwt/*.pem
###< lexik/jwt-authentication-bundle ###

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

63
.idea/Soap-bundle.iml generated Normal file
View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Malio\EdnotifBundle\" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" packagePrefix="Malio\EdnotifBundle\Tests\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/myclabs/deep-copy" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/php-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/manifest" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phar-io/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-code-coverage" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-file-iterator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-text-template" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/php-timer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/phpunit/phpunit" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/cli-parser" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/comparator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/complexity" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/diff" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/environment" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/global-state" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/lines-of-code" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-enumerator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/object-reflector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/recursion-context" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/type" />
<excludeFolder url="file://$MODULE_DIR$/vendor/sebastian/version" />
<excludeFolder url="file://$MODULE_DIR$/vendor/staabm/side-effects-detector" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/cache-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/config" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/dependency-injection" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/deprecation-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/error-handler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/event-dispatcher-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/filesystem" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/finder" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/framework-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-foundation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/http-kernel" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/options-resolver" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/phpunit-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-php85" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/routing" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/service-contracts" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-dumper" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/var-exporter" />
<excludeFolder url="file://$MODULE_DIR$/vendor/theseer/tokenizer" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/laravel-idea.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="InertiaPackage">
<option name="directoryPaths">
<list />
</option>
</component>
</project>

12
.idea/material_theme_project_new.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-3bc0fa3e:19bc6e06872:-7ff9" />
</MTProjectMetadataState>
</option>
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Soap-bundle.iml" filepath="$PROJECT_DIR$/.idea/Soap-bundle.iml" />
</modules>
</component>
</project>

81
.idea/php.xml generated Normal file
View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/psr/cache" />
<path value="$PROJECT_DIR$/vendor/psr/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/staabm/side-effects-detector" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/dependency-injection" />
<path value="$PROJECT_DIR$/vendor/symfony/service-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/options-resolver" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/symfony/var-dumper" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php85" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/symfony/http-kernel" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/symfony/phpunit-bridge" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/symfony/filesystem" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/symfony/finder" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/symfony/routing" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher-contracts" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/symfony/http-foundation" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/symfony/var-exporter" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/composer" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="8.4" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
</phpunit_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

35
composer.json Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "Malio/ednotif-bundle",
"description": "Client EDNOTIF (Guichet + wsIpBNotif) pour Symfony",
"type": "symfony-bundle",
"version": "0.0.1",
"license": "MIT",
"require": {
"php": "^8.4",
"ext-soap": "*",
"ext-xml": "*",
"symfony/framework-bundle": "^8.0",
"symfony/config": "^8.0",
"symfony/dependency-injection": "^8.0",
"symfony/http-kernel": "^8.0",
"symfony/options-resolver": "^8.0",
"symfony/cache": "^8.0",
"psr/log": "^3.0"
},
"autoload": {
"psr-4": {
"Malio\\EdnotifBundle\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Malio\\EdnotifBundle\\Tests\\": "tests/"
}
},
"require-dev": {
"phpunit/phpunit": "^12.0",
"symfony/phpunit-bridge": "^8.0"
},
"minimum-stability": "stable",
"prefer-stable": true
}

3744
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

50
config/services.php Normal file
View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
use Malio\EdnotifBundle\Api\BovinApi;
use Malio\EdnotifBundle\Api\BovinApiInterface;
use Malio\EdnotifBundle\Auth\TokenProvider;
use Malio\EdnotifBundle\Soap\SoapClientFactory;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
return static function (ContainerConfigurator $container): void {
$services = $container->services()
->defaults()
->autowire()
->autoconfigure();
$services->set(SoapClientFactory::class)
->arg('$soapOptions', '%ednotif.soap_options%');
// SoapClient Guichet
$services->set('ednotif.soap.guichet', SoapClient::class)
->factory([service(SoapClientFactory::class), 'create'])
->args(['%ednotif.guichet_wsdl%']);
// SoapClient Métier
$services->set('ednotif.soap.metier', SoapClient::class)
->factory([service(SoapClientFactory::class), 'create'])
->args(['%ednotif.metier_wsdl%']);
$services->set(TokenProvider::class)
->args([
service('ednotif.soap.guichet'),
'%ednotif.entreprise%',
'%ednotif.zone%',
'%ednotif.application%',
'%ednotif.login%',
'%ednotif.password%',
'%ednotif.token_ttl_seconds%',
service('cache.app'),
]);
$services->set(BovinApi::class)
->args([
service(TokenProvider::class),
service('ednotif.soap.metier'),
]);
$services->alias(BovinApiInterface::class, BovinApi::class)->public();
};

15
docker-compose.yml Normal file
View File

@@ -0,0 +1,15 @@
services:
php:
container_name: php-${DOCKER_APP_NAME}-fpm
build:
context: ./docker/php
dockerfile: Dockerfile
args:
DOCKER_PHP_VERSION: ${DOCKER_PHP_VERSION}
environment:
PHP_IDE_CONFIG: serverName=${DOCKER_APP_NAME}-docker
extra_hosts:
- "host.docker.internal:${CLIENT_HOST:-0.0.0.0}"
volumes:
- ./:/app
- ./docker/php/config/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

4
docker/.env.docker Normal file
View File

@@ -0,0 +1,4 @@
DOCKER_APP_NAME=ednotif-bundle
DOCKER_PHP_VERSION=8.4.6
APP_USER=www-data
XDEBUG_CLIENT_HOST=host.docker.internal

25
docker/php/Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
ARG DOCKER_PHP_VERSION
FROM php:${DOCKER_PHP_VERSION}-fpm-bullseye
# Install system dependencies
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
libxml2-dev \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug \
&& docker-php-ext-install zip \
&& docker-php-ext-install soap \
&& docker-php-ext-install dom
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# install Symfony Flex in the CI environment
RUN composer global config --no-plugins allow-plugins.symfony/flex true
RUN composer global require --no-progress --no-scripts --no-plugins symfony/flex
# Set working directory
WORKDIR /app

View File

@@ -0,0 +1,7 @@
zend_extension = /usr/local/lib/php/extensions/no-debug-non-zts-20240924/xdebug.so
xdebug.mode=debug
xdebug.idekey=PHPSTORM
xdebug.start_with_request=yes
xdebug.client_port=9003
xdebug.log_level = 0
xdebug.client_host=host.docker.internal

71
makefile Normal file
View File

@@ -0,0 +1,71 @@
# Permet d'utiliser un .env.docker.local pour override
ENV_DEFAULT = docker/.env.docker
ENV_LOCAL = docker/.env.docker.local
ENV_FILE := $(if $(wildcard $(ENV_LOCAL)),$(ENV_LOCAL),$(ENV_DEFAULT))
# Permet d'avoir les variables du fichier .env.docker.local
include $(ENV_DEFAULT)
-include $(ENV_LOCAL)
PHP_CONTAINER = php-$(DOCKER_APP_NAME)-fpm
SYMFONY_CONSOLE = $(EXEC_PHP) php bin/console
DOCKER_COMPOSE = docker compose --env-file $(ENV_FILE)
DOCKER = docker
EXEC_PHP = $(DOCKER) exec -t -u $(APP_USER) $(PHP_CONTAINER)
EXEC_PHP_CS_FIXER = $(EXEC_PHP) php vendor/bin/php-cs-fixer
EXEC_PHP_ROOT = $(DOCKER) exec -t -u root $(PHP_CONTAINER)
EXEC_PHP_INTERACTIVE = $(DOCKER) exec -it -u $(APP_USER) $(PHP_CONTAINER)
EXEC_PHP_INTERACTIVE_ROOT = $(DOCKER) exec -it -u root $(PHP_CONTAINER)
FILES =
#========================================================================================
env-init:
@mkdir -p docker
@cp --update=none $(ENV_DEFAULT) $(ENV_LOCAL)
# Lance le container
start: env-init
@echo "**** START CONTAINERS ****"
@cp --update=none docker/.env.docker docker/.env.docker.local
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
# Éteint le container
stop:
$(DOCKER_COMPOSE) stop
restart: env-init
$(DOCKER_COMPOSE) down
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
install: composer-install
# Supprime tout est réinstalle tout (Attention ça supprime la bdd aussi)
reset: delete_built_dir remove_orphans build-without-cache start wait install
composer-install:
$(EXEC_PHP) composer install
delete_built_dir:
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
$(DOCKER) exec -u root $(PHP_CONTAINER) rm -rf vendor/
remove_orphans:
$(DOCKER_COMPOSE) kill
$(DOCKER_COMPOSE) down --volumes --remove-orphans
build-without-cache:
$(DOCKER_COMPOSE) build \
--build-arg="DOCKER_PHP_VERSION=$(DOCKER_PHP_VERSION)" \
--build-arg="DOCKER_NODE_VERSION=$(DOCKER_NODE_VERSION)" \
--build-arg="CURRENT_UID=$(shell id -u)" \
--build-arg="CURRENT_GID=$(shell id -g)" \
--no-cache
shell:
$(EXEC_PHP_INTERACTIVE) bash
wait:
sleep 10

112
src/Api/BovinApi.php Normal file
View File

@@ -0,0 +1,112 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Api;
use Malio\EdnotifBundle\Auth\TokenProvider;
use Malio\EdnotifBundle\Dto\DossierAnimalDto;
use Malio\EdnotifBundle\Exception\EdnotifException;
use SoapClient;
use SoapFault;
final class BovinApi implements BovinApiInterface
{
public function __construct(
private TokenProvider $tokenProvider,
private SoapClient $metierClient,
) {
}
public function getDossierAnimal(string $exploitationNumero, string $numeroNational, string $codePays = 'FR'): DossierAnimalDto
{
$token = $this->tokenProvider->getToken();
$payload = [[
'JetonAuthentification' => $token,
'Exploitation' => [
'CodePays' => $codePays,
'NumeroExploitation' => $exploitationNumero,
],
'Bovin' => [
'CodePays' => $codePays,
'NumeroNational' => $numeroNational,
],
]];
try {
/** @var object $response */
$response = $this->metierClient->__soapCall('IpBGetDossierAnimal', $payload);
} catch (SoapFault $e) {
// Si cest un souci de jeton, tu peux invalider et retenter une fois (optionnel)
throw new \RuntimeException('SOAP Fault lors de IpBGetDossierAnimal: ' . $e->getMessage(), 0, $e);
}
$rs = $response->ReponseStandard ?? null;
$ok = is_object($rs) && (($rs->Resultat ?? false) === true);
if (!$ok) {
$anom = $rs->Anomalie ?? null;
$code = (string)($anom->Code ?? 'UNKNOWN');
$sev = (int)($anom->Severite ?? 1);
$msg = (string)($anom->Message ?? 'Appel EDNOTIF refusé');
throw new EdnotifException($code, $sev, $msg);
}
$identite = [];
$periodes = [];
$bovinNode = $response->ReponseSpecifique->Bovin ?? null;
if (is_object($bovinNode)) {
$identiteObj = $bovinNode->IdentiteBovin ?? null;
if (is_object($identiteObj)) {
$identite = $this->objectToArray($identiteObj);
}
$pp = $bovinNode->PeriodesPresences->PeriodePresence ?? null;
foreach ($this->normalizeList($pp) as $periode) {
if (!is_object($periode)) {
continue;
}
$entree = is_object($periode->Entree ?? null) ? $this->objectToArray($periode->Entree) : [];
$sortie = is_object($periode->Sortie ?? null) ? $this->objectToArray($periode->Sortie) : null;
$row = ['entree' => $entree];
if ($sortie !== null) {
$row['sortie'] = $sortie;
}
$periodes[] = $row;
}
}
return new DossierAnimalDto(
numeroNational: $numeroNational,
identiteBovin: $identite,
periodesPresence: $periodes,
rawResponse: $response,
);
}
/**
* @return list<mixed>
*/
private function normalizeList(mixed $value): array
{
if ($value === null) {
return [];
}
if (is_array($value)) {
return $value;
}
return [$value];
}
/**
* @return array<string,mixed>
*/
private function objectToArray(object $obj): array
{
// conversion simple (suffisante pour démarrer)
return json_decode(json_encode($obj, JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR);
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Api;
use Malio\EdnotifBundle\Dto\DossierAnimalDto;
interface BovinApiInterface
{
public function getDossierAnimal(
string $exploitationNumero,
string $numeroNational,
string $codePays = 'FR'
): DossierAnimalDto;
}

View File

@@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Auth;
use Malio\EdnotifBundle\Exception\EdnotifException;
use Psr\Cache\CacheItemPoolInterface;
use SoapClient;
use SoapFault;
final class TokenProvider
{
public function __construct(
private SoapClient $guichetClient,
private string $entreprise,
private ?string $zone,
private ?string $application,
private string $login,
private string $password,
private int $tokenTtlSeconds,
private CacheItemPoolInterface $cachePool,
) {
}
public function getToken(): string
{
$cacheKey = $this->getCacheKey();
$item = $this->cachePool->getItem($cacheKey);
if ($item->isHit()) {
$token = $item->get();
if (is_string($token) && $token !== '') {
return $token;
}
}
$token = $this->createToken();
$item->set($token);
$item->expiresAfter($this->tokenTtlSeconds);
$this->cachePool->save($item);
return $token;
}
public function invalidateToken(): void
{
$this->cachePool->deleteItem($this->getCacheKey());
}
private function createToken(): string
{
$profil = array_filter([
'Entreprise' => $this->entreprise,
'Zone' => $this->zone,
'Application' => $this->application,
], static fn ($v) => $v !== null && $v !== '');
$payload = [
'Identification' => [
'UserId' => $this->login,
'Password' => $this->password,
'Profil' => $profil,
],
];
try {
/** @var object $response */
$response = $this->guichetClient->__soapCall('tkCreateIdentification', [$payload]);
} catch (SoapFault $e) {
throw new \RuntimeException('SOAP Fault lors de tkCreateIdentification: ' . $e->getMessage(), 0, $e);
}
$rs = $response->ReponseStandard ?? null;
$ok = is_object($rs) && (($rs->Resultat ?? false) === true);
if (!$ok) {
$anom = $rs->Anomalie ?? null;
$code = (string)($anom->Code ?? 'UNKNOWN');
$sev = (int)($anom->Severite ?? 1);
$msg = (string)($anom->Message ?? 'Authentification refusée');
throw new EdnotifException($code, $sev, $msg);
}
$token = $response->Jeton ?? null;
if (!is_string($token) || $token === '') {
throw new \RuntimeException('Guichet: réponse OK mais Jeton absent.');
}
return $token;
}
private function getCacheKey(): string
{
return 'ednotif.token.' . hash('sha256', $this->entreprise . '|' . $this->login);
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
final class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder('ednotif');
$root = $treeBuilder->getRootNode();
$root
->children()
->scalarNode('guichet_wsdl')->cannotBeEmpty()->isRequired()->end()
->scalarNode('metier_wsdl')->cannotBeEmpty()->isRequired()->end()
->scalarNode('entreprise')->cannotBeEmpty()->isRequired()->end()
->scalarNode('zone')->defaultNull()->end()
->scalarNode('application')->defaultNull()->end()
->scalarNode('login')->cannotBeEmpty()->isRequired()->end()
->scalarNode('password')->cannotBeEmpty()->isRequired()->end()
->integerNode('token_ttl_seconds')->min(30)->defaultValue(900)->end()
->arrayNode('soap_options')
->addDefaultsIfNotSet()
->children()
->booleanNode('trace')->defaultFalse()->end()
->booleanNode('exceptions')->defaultTrue()->end()
->integerNode('connection_timeout')->min(1)->defaultValue(15)->end()
->integerNode('cache_wsdl')->defaultValue(\WSDL_CACHE_BOTH)->end()
->integerNode('features')->defaultValue(\SOAP_SINGLE_ELEMENT_ARRAYS)->end()
->end()
->end()
->end();
return $treeBuilder;
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
final class EdnotifExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container): void
{
$configuration = new Configuration();
/** @var array{
* guichet_wsdl:string,
* metier_wsdl:string,
* entreprise:string,
* zone:?string,
* application:?string,
* login:string,
* password:string,
* token_ttl_seconds:int,
* soap_options:array<string,mixed>
* } $config
*/
$config = $this->processConfiguration($configuration, $configs);
$container->setParameter('ednotif.guichet_wsdl', $config['guichet_wsdl']);
$container->setParameter('ednotif.metier_wsdl', $config['metier_wsdl']);
$container->setParameter('ednotif.entreprise', $config['entreprise']);
$container->setParameter('ednotif.zone', $config['zone']);
$container->setParameter('ednotif.application', $config['application']);
$container->setParameter('ednotif.login', $config['login']);
$container->setParameter('ednotif.password', $config['password']);
$container->setParameter('ednotif.token_ttl_seconds', $config['token_ttl_seconds']);
$container->setParameter('ednotif.soap_options', $config['soap_options']);
$loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../../config'));
$loader->load('services.php');
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Dto;
final readonly class DossierAnimalDto
{
/**
* @param array<string,mixed> $identiteBovin
* @param list<array{entree: array<string,mixed>, sortie?: array<string,mixed>}> $periodesPresence
*/
public function __construct(
public string $numeroNational,
public array $identiteBovin,
public array $periodesPresence,
public object $rawResponse,
) {
}
}

11
src/EdnotifBundle.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
final class EdnotifBundle extends Bundle
{
}

View File

@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Exception;
use RuntimeException;
final class EdnotifException extends RuntimeException
{
public function __construct(
public readonly string $codeAnomalie,
public readonly int $severite,
string $message
) {
parent::__construct($message);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Malio\EdnotifBundle\Soap;
use SoapClient;
final class SoapClientFactory
{
/**
* @param array<string,mixed> $soapOptions
*/
public function __construct(private array $soapOptions = [])
{
}
public function create(string $wsdl): SoapClient
{
$options = $this->soapOptions;
// Petites valeurs sûres par défaut
$options['encoding'] ??= 'UTF-8';
$options['exceptions'] ??= true;
return new SoapClient($wsdl, $options);
}
}