devmachine/guzzle-markus-client
Guzzle-powered PHP client for Markus Cinema System (MCS) XML APIs used by Forum Cinemas/Finnkino and others. Normalizes inconsistent XML, renames/regroups fields, and returns cleaner structured data for areas, events (movies) and shows.
Installation:
composer require devmachine/guzzle-markus-client
Add the package to your composer.json under require.
Initialize Client:
use Devmachine\Guzzle\Markus\MarkusClient;
$client = MarkusClient::factory('http://forumcinemas.lv/XML');
First Use Case: Fetch cinema areas (locations) to understand the structure:
$areas = $client->areas();
dd($areas['items']);
This returns an array of cinema locations (e.g., cities or theaters) with id and name.
Fetching Data Hierarchically:
areas() to identify locations, then use their IDs to filter other endpoints.$events = $client->events(['area' => '1002']); // '1002' is Tallinn's area ID
Handling Dates and Shows:
schedule() to get available dates, then shows() to fetch showtimes for a specific date/event:
$dates = $client->schedule(['area' => '1002']);
$shows = $client->shows([
'area' => '1002',
'event' => '1234', // Movie ID
'date' => '2023-10-15'
]);
Media-Rich Data:
$event = $client->events([
'id' => '1234',
'include_videos' => true,
'include_gallery' => true,
'all_images' => true
]);
Pagination (Implicit):
items and total keys. Loop through pages by adjusting query parameters (e.g., offset/limit if supported by the underlying endpoint).Caching Responses:
$areas = Cache::remember('markus_areas', now()->addHours(1), function () use ($client) {
return $client->areas();
});
Error Handling:
try {
$data = $client->events(['area' => '1002']);
} catch (\Exception $e) {
Log::error("Markus API Error: " . $e->getMessage());
return response()->json(['error' => 'Service unavailable'], 503);
}
Dynamic Endpoint Selection:
config(['markus.endpoints' => [
'latvia' => 'http://forumcinemas.lv/XML',
'estonia' => 'http://forumcinemas.ee/XML',
]]);
Then use:
$client = MarkusClient::factory(config('markus.endpoints.latvia'));
Data Transformation:
collect() to reshape data for your application:
$shows = collect($client->shows(['area' => '1002']))
->map(function ($show) {
return [
'theater' => $show['theater_name'],
'time' => Carbon::parse($show['start'])->format('H:i'),
'price' => $show['price'] ?? 'N/A'
];
});
Inconsistent API Responses:
articles, events) may return incomplete or malformed data for certain cinemas (e.g., edencinemas.com.mt). Validate responses:
if (empty($result['items'])) {
throw new \RuntimeException("No data returned for the given filters.");
}
Date Handling:
date parameter in shows() expects YYYY-MM-DD format. Use Carbon for validation:
$date = Carbon::parse($request->date)->format('Y-m-d');
Missing IDs:
articles) require an event ID to filter by movie. Ensure you fetch this first via events().Image URLs:
$baseUrl = 'http://forumcinemas.lv';
$imageUrl = $baseUrl . $event['images']['large']['portrait'];
Raw XML Inspection:
$response = $client->getGuzzleClient()->get('http://forumcinemas.lv/XML');
$xml = $response->getBody();
file_put_contents('debug.xml', $xml);
Logging API Calls:
$client = MarkusClient::factory('http://forumcinemas.lv/XML');
$client->getGuzzleClient()->getEmitter()->attach(
Subscriber::create()->tap(function ($request, $response) {
Log::debug('Markus API Request:', [
'url' => $request->getUri(),
'response' => $response->getBody()
]);
})
);
Custom Endpoints:
MarkusClient:
class CustomMarkusClient extends MarkusClient {
public function customEndpoint() {
return $this->request('GET', '/custom/path');
}
}
Additional Methods:
public function upcomingShows($areaId, $days = 7) {
$today = now()->format('Y-m-d');
$endDate = now()->addDays($days)->format('Y-m-d');
return $this->shows([
'area' => $areaId,
'date' => $today,
'days_from_date' => $days
]);
}
Rate Limiting:
DelayMiddleware:
$client = MarkusClient::factory('http://forumcinemas.lv/XML');
$client->getGuzzleClient()->getEmitter()->attach(
new \GuzzleHttp\Middleware::delay(100) // 100ms delay between requests
);
Testing:
Mockery:
$mockClient = Mockery::mock(MarkusClient::class);
$mockClient->shouldReceive('events')->once()->andReturn(['items' => []]);
$this->app->instance(MarkusClient::class, $mockClient);
How can I help you explore Laravel packages today?