Files
jobifygarden/app/Services/JobSearchService.php
T

236 lines
8.0 KiB
PHP
Raw Normal View History

2026-04-03 00:39:57 +03:00
<?php
namespace App\Services;
use App\Models\Job;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
2026-05-02 14:52:55 +03:00
use Illuminate\Support\Facades\Request;
2026-04-03 00:39:57 +03:00
class JobSearchService
{
2026-04-03 20:55:51 +03:00
public function __construct(
private CacheService $cacheService
) {}
2026-04-03 00:39:57 +03:00
/**
* Search jobs with advanced filters
*/
public function search(array $filters = [], int $perPage = 15): LengthAwarePaginator
{
2026-05-02 14:52:55 +03:00
$page = Request::input('page', 1);
$cacheKey = $this->cacheService->searchResultsKey($filters, (int) $page);
2026-04-03 00:39:57 +03:00
2026-05-02 14:52:55 +03:00
$cached = $this->cacheService->remember($cacheKey, CacheService::CACHE_SEARCH_RESULTS, function () use ($filters, $perPage) {
$query = Job::query()
->with(['source', 'category', 'jobLocation'])
->active();
2026-04-03 00:39:57 +03:00
2026-05-02 14:52:55 +03:00
$this->applyFilters($query, $filters);
$this->applySorting($query, $filters['sort'] ?? 'date');
2026-04-03 00:39:57 +03:00
2026-05-02 14:52:55 +03:00
return $query->paginate($perPage);
});
return $cached;
2026-04-03 00:39:57 +03:00
}
/**
* Apply filters to the query
*/
private function applyFilters(Builder $query, array $filters): void
{
// Keyword search (title, company, description)
if (!empty($filters['keywords'])) {
$keywords = $filters['keywords'];
$query->where(function ($q) use ($keywords) {
$q->where('title', 'LIKE', "%{$keywords}%")
->orWhere('company', 'LIKE', "%{$keywords}%")
->orWhere('description', 'LIKE', "%{$keywords}%");
});
}
// Full-text search (if supported)
if (!empty($filters['search'])) {
$search = $filters['search'];
$query->whereRaw(
"MATCH(title, company, description) AGAINST(? IN BOOLEAN MODE)",
[$search]
);
}
// Location filter
if (!empty($filters['location'])) {
$location = $filters['location'];
$query->where(function ($q) use ($location) {
$q->where('location', 'LIKE', "%{$location}%")
->orWhereHas('jobLocation', function ($q2) use ($location) {
$q2->where('city', 'LIKE', "%{$location}%")
->orWhere('state', 'LIKE', "%{$location}%")
->orWhere('country', 'LIKE', "%{$location}%");
});
});
}
// Category filter
if (!empty($filters['category_id'])) {
$query->where('job_category_id', $filters['category_id']);
}
// Job type filter (can be array for multiple types)
if (!empty($filters['job_type'])) {
$types = is_array($filters['job_type']) ? $filters['job_type'] : [$filters['job_type']];
$query->whereIn('job_type', $types);
}
// Experience level filter (can be array)
if (!empty($filters['experience_level'])) {
$levels = is_array($filters['experience_level']) ? $filters['experience_level'] : [$filters['experience_level']];
$query->whereIn('experience_level', $levels);
}
// Remote filter
if (isset($filters['remote']) && $filters['remote'] !== '') {
$query->where('remote_allowed', (bool) $filters['remote']);
}
// Salary range filter
if (!empty($filters['salary_min'])) {
$query->where(function ($q) use ($filters) {
$q->where('salary_max', '>=', $filters['salary_min'])
->orWhereNull('salary_max');
});
}
if (!empty($filters['salary_max'])) {
$query->where(function ($q) use ($filters) {
$q->where('salary_min', '<=', $filters['salary_max'])
->orWhereNull('salary_min');
});
}
// Posted date filter (e.g., last 24 hours, 7 days, 30 days)
if (!empty($filters['posted_within'])) {
$days = (int) $filters['posted_within'];
$query->where(function ($q) use ($days) {
$q->where('posted_at', '>=', now()->subDays($days))
->orWhere(function ($q2) use ($days) {
$q2->whereNull('posted_at')
->where('created_at', '>=', now()->subDays($days));
});
});
}
// Source filter
if (!empty($filters['source_id'])) {
$query->where('job_source_id', $filters['source_id']);
}
// Company filter
if (!empty($filters['company'])) {
$query->where('company', 'LIKE', "%{$filters['company']}%");
}
}
/**
* Apply sorting to the query
*/
private function applySorting(Builder $query, string $sort): void
{
match ($sort) {
'date' => $query->latest('posted_at'),
'date_asc' => $query->oldest('posted_at'),
'salary_desc' => $query->orderBy('salary_max', 'desc')->orderBy('salary_min', 'desc'),
'salary_asc' => $query->orderBy('salary_min', 'asc')->orderBy('salary_max', 'asc'),
'title' => $query->orderBy('title', 'asc'),
'company' => $query->orderBy('company', 'asc'),
'relevance' => $query->latest('created_at'), // Default to newest if no full-text score
default => $query->latest('posted_at'),
};
}
/**
* Get autocomplete suggestions for job titles
*/
public function getTitleSuggestions(string $query, int $limit = 10): array
{
return Job::select('title')
->where('title', 'LIKE', "{$query}%")
->active()
->groupBy('title')
->limit($limit)
->pluck('title')
->toArray();
}
/**
* Get autocomplete suggestions for companies
*/
public function getCompanySuggestions(string $query, int $limit = 10): array
{
return Job::select('company')
->where('company', 'LIKE', "{$query}%")
->active()
->groupBy('company')
->limit($limit)
->pluck('company')
->toArray();
}
/**
* Get autocomplete suggestions for locations
*/
public function getLocationSuggestions(string $query, int $limit = 10): array
{
return Job::select('location')
->where('location', 'LIKE', "{$query}%")
->whereNotNull('location')
->active()
->groupBy('location')
->limit($limit)
->pluck('location')
->toArray();
}
/**
* Get available filter options
*/
public function getFilterOptions(): array
{
2026-04-03 20:55:51 +03:00
$cacheKey = $this->cacheService->filterOptionsKey();
return $this->cacheService->remember($cacheKey, CacheService::CACHE_FILTER_OPTIONS, function () {
// Get actual distinct values from database for job types and experience levels
$jobTypes = Job::select('job_type')
->whereNotNull('job_type')
->distinct()
->pluck('job_type')
->toArray();
$experienceLevels = Job::select('experience_level')
->whereNotNull('experience_level')
->distinct()
->pluck('experience_level')
->toArray();
return [
'job_types' => $jobTypes,
'experience_levels' => $experienceLevels,
'posted_within' => [
1 => 'Last 24 hours',
7 => 'Last 7 days',
30 => 'Last 30 days',
90 => 'Last 3 months',
],
'sort_options' => [
'date' => 'Most Recent',
'date_asc' => 'Oldest First',
'salary_desc' => 'Highest Salary',
'salary_asc' => 'Lowest Salary',
'title' => 'Title A-Z',
'company' => 'Company A-Z',
],
];
});
2026-04-03 00:39:57 +03:00
}
}