Files
jobifygarden/app/Services/JobApi/Transformers/AdzunaTransformer.php
T
2026-04-04 20:37:35 +03:00

118 lines
3.9 KiB
PHP

<?php
namespace App\Services\JobApi\Transformers;
use App\Services\JobApi\DTO\JobData;
use App\Services\JobApi\Exceptions\TransformationException;
use DateTime;
class AdzunaTransformer implements JobTransformer
{
public function transform(array $data): JobData
{
try {
return new JobData(
externalId: (string) $data['id'],
title: $data['title'] ?? 'Untitled',
company: $data['company']['display_name'] ?? 'Unknown Company',
description: $this->sanitizeDescription($data['description'] ?? ''),
location: $this->parseLocation($data),
salaryMin: $data['salary_min'] ?? null,
salaryMax: $data['salary_max'] ?? null,
salaryCurrency: 'USD',
jobType: $this->mapJobType($data['contract_type'] ?? null),
sourceUrl: $data['redirect_url'] ?? null,
applyUrl: $data['redirect_url'] ?? null,
postedAt: $this->parseDate($data['created'] ?? null),
expiresAt: null,
remoteAllowed: $this->isRemote($data),
experienceLevel: null,
category: $data['category']['label'] ?? null,
rawData: $data,
);
} catch (\Throwable $e) {
throw new TransformationException(
'Adzuna',
'Failed to transform Adzuna job data: ' . $e->getMessage(),
['data' => $data, 'error' => $e->getMessage()]
);
}
}
public function transformMany(array $jobs): array
{
return array_map(fn($job) => $this->transform($job), $jobs);
}
private function sanitizeDescription(string $description): string
{
// Adzuna returns truncated descriptions with ellipsis (...)
// Keep the description as-is but strip excessive HTML while preserving basic formatting
$clean = strip_tags($description, '<p><br><ul><ol><li><strong><b><em><i><h1><h2><h3><h4>');
$clean = html_entity_decode($clean, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Convert multiple spaces to single space but preserve newlines
$clean = preg_replace('/[^\S\n]+/', ' ', $clean);
// Note: Adzuna API only provides truncated descriptions in search results.
// Full descriptions would require individual job detail API calls.
return trim($clean);
}
private function parseLocation(array $data): ?string
{
$location = $data['location']['display_name'] ?? null;
if (empty($location)) {
// Fallback to area
$area = $data['location']['area'] ?? [];
if (!empty($area)) {
return implode(', ', $area);
}
}
return $location;
}
private function isRemote(array $data): bool
{
$title = strtolower($data['title'] ?? '');
$description = strtolower($data['description'] ?? '');
$location = strtolower($data['location']['display_name'] ?? '');
// this gives false positives, find a better way, until then should be false
// return str_contains($title, 'remote') ||
// str_contains($description, 'remote') ||
// str_contains($location, 'remote');
return false;
}
private function parseDate(?string $date): ?DateTime
{
if (empty($date)) {
return null;
}
try {
return new DateTime($date);
} catch (\Exception $e) {
return null;
}
}
private function mapJobType(?string $type): ?string
{
if (empty($type)) {
return null;
}
return match (strtolower($type)) {
'permanent', 'full_time' => 'full-time',
'part_time' => 'part-time',
'contract' => 'contract',
'temporary' => 'temporary',
default => 'full-time',
};
}
}