I have to implement a "user-friendly" URL for an employment website. The URL /jobs{parameters?}
may be composed of up to three searching criteria separated by hyphens (i.e. -
); each criterion is separated by a tilde (i.e. ~
). The parameters
string has the following structure: -keywords-employmentTypes-locations
. keywords
and employmentTypes
are optional. location
is mandatory unless there are no parameters, the route should handle all these cases:
/jobs
/jobs-canada /jobs-canada~paris~usa
/jobs-full_time-canada /jobs-full_time~part_time-canada~paris
/jobs-engineer-canada /jobs-engineer~c++-canada~paris
/jobs-engineer-full_time-canada /jobs-engineer~c++-full_time-canada~paris
The following class parses the string parameters
and fill all criteria in variables:
class JobUrlBuilder
{
const DEFAULT_LOCATION = 'france';
public $keywords;
public $employmentTypes;
public $locations;
public $allEmploymentTypes;
function __construct($allEmploymentTypes = [])
{
$this->setAllEmploymentTypes($allEmploymentTypes);
}
/**
* @param string $parameters
*/
public function setParameters($parameters)
{
$parameters = explode('-', ltrim($parameters, '-'));
if (empty($parameters[0])) {
array_shift($parameters);
}
if (count($parameters) === 3) {
$this->keywords = $parameters[0];
$this->employmentTypes = $parameters[1];
$this->locations = $parameters[2];
} else if (count($parameters) === 2) {
$areEmploymentTypes = $this->areEmploymentTypes(explode('~', $parameters[0]));
$this->keywords = $areEmploymentTypes ? null : $parameters[0];
$this->employmentTypes = $areEmploymentTypes ? $parameters[0] : null;
$this->locations = $parameters[1];
} else if (count($parameters) === 1) {
$this->keywords = null;
$this->employmentTypes = null;
$this->locations = $parameters[0];
} else {
$this->keywords = null;
$this->employmentTypes = null;
$this->locations = self::DEFAULT_LOCATION;
}
$this->keywords = $this->keywords ? explode('~', $this->keywords) : [];
$this->employmentTypes = $this->employmentTypes ? explode('~', $this->employmentTypes) : [];
$this->locations = $this->locations ? explode('~', $this->locations) : [];
return $this;
}
/**
* @param array $items
*/
public function areEmploymentTypes($items)
{
$jobTypes = array_map(function ($v)
{
return mb_strtolower($v);
}, $this->allEmploymentTypes);
return ! array_diff($items, $jobTypes);
}
/**
* @param array $employmentTypes
*/
public function setAllEmploymentTypes($employmentTypes)
{
$this->allEmploymentTypes = $employmentTypes;
}
public function hasDefaultLocation()
{
return in_array(self::DEFAULT_LOCATION, $this->locations);
}
}
I don't like this code (spaghetti code, no separation of concerns...). I think there is a better implementation. I'm also open to other URL structures - I know I can simply use input parameters and call it a day, but I should implement a clean URL. I have added unit tests to handle all cases. I think it might help you understand the code.
keywords
andemploymentTypes
individually optional? If so, You'll struggle to tell the difference between/jobs-fulltime-canada
and/jobs-php-canada
for example. FWIW I think you should just use input parameters, it's not like anyone is going to type this out. \$\endgroup\$/jobs-fulltime-canada
and/jobs-fulltime-php
, yes it's ambiguous but I don't mind in this case. Probably I'm over-engineering, I may reconsider my code, thanks. \$\endgroup\$