feat: parser and loader for insurance data

This commit is contained in:
Björn Fromme
2025-09-24 15:28:43 +02:00
parent 582fb9b52d
commit 9d44197246
3 changed files with 325 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\XmlParser;
use App\BusProNet\Model\Insurance;
use Symfony\Component\DomCrawler\Crawler;
/**
* Parses insurance XML data into Insurance model objects.
*
* Handles both individual insurance products (versicherung) and insurance packages
* (versicherungspaket) with attribute-based parsing, type conversion, and filtering logic.
*/
class InsuranceParser extends AbstractParser
{
/**
* Parses insurance XML nodes into Insurance objects.
*
* @param Crawler $xmlContent The XML crawler containing insurance data
* @return array<string|int, Insurance> Array of Insurance objects indexed by id
*/
public function parse(Crawler $xmlContent): array
{
$insurances = [];
// First pass: collect referenced insurance IDs from packages
$referencedIds = $this->collectReferencedInsuranceIds($xmlContent);
// Parse individual insurances with conditional filtering
$xmlContent->filterXPath('//versicherungen/versicherung')
->each(function (Crawler $node) use (&$insurances, $referencedIds) {
$id = (int) $node->attr('idbuspro'); // Individual insurances have int IDs
$isZusatz = $this->getBoolAttributeValue($node->attr('zusatzversicherung'));
// Include if: not zusatzversicherung OR referenced by package
if (!$isZusatz || in_array($id, $referencedIds, true)) {
$insurance = $this->parseInsuranceNode($node, false);
if (null !== $insurance && null !== $insurance->id) {
$insurances[$insurance->id] = $insurance;
}
}
});
// Parse packages (no filtering needed)
$xmlContent->filterXPath('//versicherungspakete/versicherungspaket')
->each(function (Crawler $node) use (&$insurances) {
$insurance = $this->parseInsuranceNode($node, true);
if (null !== $insurance && null !== $insurance->id) {
// Parse contained insurance IDs
$insurance->containedInsuranceIds = $this->parseContainedInsuranceIds($node);
$insurances[$insurance->id] = $insurance;
}
});
return $insurances;
}
/**
* Parses a single insurance or package node.
*
* @param Crawler $node The XML node to parse
* @param bool $isPackage Whether this is a package node
* @return Insurance|null The parsed insurance object
*/
private function parseInsuranceNode(Crawler $node, bool $isPackage): ?Insurance
{
$insurance = new Insurance();
$insurance->package = $isPackage;
// Basic identifiers - packages have string IDs with 'P' prefix, individual insurances have int IDs
$idValue = $node->attr('idbuspro');
$insurance->id = $isPackage ? $idValue : (int) $idValue;
$insurance->code = $node->attr('code');
$insurance->label = $node->attr('bezeichnung');
$insurance->price = $node->attr('preis') ?
$this->stringToFloat($node->attr('preis')) : null;
$insurance->familyInsurance = $this->stringToBool($node->attr('familienversicherung'));
// Date constraints
$insurance->travelDateFrom = $node->attr('reisedatumvon') ?
$this->stringToDate($node->attr('reisedatumvon')) : null;
$insurance->travelDateTo = $node->attr('reisedatumbis') ?
$this->stringToDate($node->attr('reisedatumbis')) : null;
$insurance->bookingDateFrom = $node->attr('buchungdatumvon') ?
$this->stringToDate($node->attr('buchungdatumvon')) : null;
$insurance->bookingDateTo = $node->attr('buchungdatumbis') ?
$this->stringToDate($node->attr('buchungdatumbis')) : null;
// Age constraints
$insurance->ageFrom = $node->attr('altervon') ? (int) $node->attr('altervon') : null;
$insurance->ageTo = $node->attr('alterbis') ? (int) $node->attr('alterbis') : null;
// Price constraints
$insurance->travelPriceFrom = $node->attr('reisepreisvon') ?
$this->stringToFloat($node->attr('reisepreisvon')) : null;
$insurance->travelPriceTo = $node->attr('reisepreisbis') ?
$this->stringToFloat($node->attr('reisepreisbis')) : null;
// Duration constraints
$insurance->travelDurationFrom = $node->attr('reisedauervon') ? (int) $node->attr('reisedauervon') : null;
$insurance->travelDurationTo = $node->attr('reisedauerbis') ? (int) $node->attr('reisedauerbis') : null;
// Information URLs
$insurance->urlInfo = $node->attr('urlinfo');
$insurance->urlProductInfo = $node->attr('urlproduktinfo');
$insurance->urlTerms = $node->attr('urlagb');
return $insurance;
}
/**
* Collects all insurance IDs referenced by packages.
*
* @param Crawler $xmlContent The XML content to scan
* @return array<int> Array of referenced insurance IDs
*/
private function collectReferencedInsuranceIds(Crawler $xmlContent): array
{
$referencedIds = [];
$xmlContent->filterXPath('//versicherungspakete/versicherungspaket')
->each(function (Crawler $packageNode) use (&$referencedIds) {
$packageNode->filterXPath('.//enthalteneversicherung')
->each(function (Crawler $refNode) use (&$referencedIds) {
$referencedIds[] = (int) $refNode->attr('idbuspro'); // Referenced insurances are always int IDs
});
});
return array_unique($referencedIds);
}
/**
* Parses contained insurance IDs from a package node.
*
* @param Crawler $node The package XML node
* @return array<int> Array of contained insurance IDs (always int, as they reference individual insurances)
*/
private function parseContainedInsuranceIds(Crawler $node): array
{
$ids = [];
$node->filterXPath('.//enthalteneversicherungen/enthalteneversicherung')
->each(function (Crawler $insuranceNode) use (&$ids) {
$ids[] = (int) $insuranceNode->attr('idbuspro'); // Individual insurance IDs are always int
});
return $ids;
}
private function getBoolAttributeValue(?string $value): bool
{
return !empty($value) && $this->stringToBool($value);
}
}