128 lines
4.0 KiB
PHP
128 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\BusProNet\XmlLoader;
|
|
|
|
use App\BusProNet\Model\Hotel;
|
|
use App\BusProNet\Model\Travel;
|
|
use App\BusProNet\Utility\CountryCodeMapper;
|
|
use App\BusProNet\XmlCrawlerFactory;
|
|
use App\Exception\HotelNotFoundException;
|
|
use League\Flysystem\FilesystemOperator;
|
|
use Psr\Cache\InvalidArgumentException;
|
|
use Symfony\Component\DomCrawler\Crawler;
|
|
use Symfony\Contracts\Cache\CacheInterface;
|
|
use Symfony\Contracts\Cache\ItemInterface;
|
|
|
|
class HotelLoader extends AbstractLoader
|
|
{
|
|
public function __construct(
|
|
private readonly CountryCodeMapper $countryCodeMapper,
|
|
CacheInterface $cache,
|
|
FilesystemOperator $xmlExport,
|
|
) {
|
|
parent::__construct($cache, $xmlExport);
|
|
}
|
|
|
|
/** @return array<int, Hotel> */
|
|
public function loadAll(?string $filename = 'hotel.xml'): array
|
|
{
|
|
try {
|
|
return $this->cache->get('bpn_hotels', function (ItemInterface $item) use ($filename) {
|
|
$item->expiresAfter(3 * 60 * 60);
|
|
$item->tag(['xml-sync']);
|
|
|
|
$hotels = [];
|
|
|
|
$this
|
|
->loadXml($filename)
|
|
->filterXPath('//hotels/hotel')
|
|
->each(function (Crawler $node) use (&$hotels) {
|
|
$id = (int) $node->attr('idbuspro');
|
|
$hotels[$id] = $this->parseXml($node);
|
|
})
|
|
;
|
|
|
|
return $hotels;
|
|
});
|
|
} catch (InvalidArgumentException $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public function mapCodeToId(string $hotelCode, ?string $filename = 'hotel.xml'): ?int
|
|
{
|
|
return $this->loadByCode($hotelCode, $filename)?->id;
|
|
}
|
|
|
|
public function loadByCode(string $hotelCode, ?string $filename = 'hotel.xml'): ?Hotel
|
|
{
|
|
$hotels = $this->loadAll($filename);
|
|
|
|
foreach ($hotels as $hotel) {
|
|
if ($hotelCode === $hotel->code) {
|
|
return $hotel;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @throws HotelNotFoundException
|
|
*/
|
|
public function loadById(int $id, ?string $filename = 'hotel.xml'): Hotel
|
|
{
|
|
$hotels = $this->loadAll($filename);
|
|
|
|
if (false === isset($hotels[$id])) {
|
|
throw new HotelNotFoundException($id);
|
|
}
|
|
|
|
return $hotels[$id];
|
|
}
|
|
|
|
private function loadXml(?string $filename = 'hotel.xml'): Crawler
|
|
{
|
|
$xml = $this->xmlExport->read($filename);
|
|
|
|
return XmlCrawlerFactory::create($xml);
|
|
}
|
|
|
|
public function parseXml(Crawler $node): Hotel
|
|
{
|
|
$hotel = new Hotel();
|
|
$hotel->id = (int) $node->attr('idbuspro');
|
|
$hotel->code = $node->attr('code');
|
|
$hotel->name = $this->getStringOrNullValue($node->filterXPath('//name'));
|
|
$hotel->city = $this->getStringOrNullValue($node->filterXPath('//ort'));
|
|
$hotel->country = $this->getStringOrNullValue($node->filterXPath('//land'));
|
|
$hotel->street = $this->getStringOrNullValue($node->filterXPath('//strasse'));
|
|
$hotel->phone = $this->getStringOrNullValue($node->filterXPath('//telefon'));
|
|
// String, never int: see Hotel::$costUnit.
|
|
$hotel->costUnit = $this->getStringOrNullValue($node->filterXPath('//kostenstelle'));
|
|
// `country` itself stays the raw token — it is the join key into /api/countries[].token.
|
|
$hotel->countryCode = $this->countryCodeMapper->map($hotel->country);
|
|
|
|
return $hotel;
|
|
}
|
|
|
|
public function patchHotelDetails(Travel $travel): void
|
|
{
|
|
$hotelId = $travel->hotelId;
|
|
if (null === $hotelId) {
|
|
$travel->hotel = null;
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$hotel = $this->loadById($hotelId);
|
|
$travel->hotel = $hotel;
|
|
} catch (HotelNotFoundException $e) {
|
|
// If hotel details can't be loaded, leave travel->hotel as null
|
|
// This allows the travel to be processed even if hotel details are missing
|
|
$travel->hotel = null;
|
|
}
|
|
}
|
|
}
|