Completed
Branch v1 (d74520)
by Karl
01:43
created

JobsMulti::setKeyword()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
crap 1
1
<?php namespace JobApis\Jobs\Client;
2
3
use JobApis\Jobs\Client\Providers\AbstractProvider;
4
use JobApis\Jobs\Client\Queries\AbstractQuery;
5
6
class JobsMulti
7
{
8
    /**
9
     * Search keyword
10
     *
11
     * @var string
12
     */
13
    protected $keyword;
14
15
    /**
16
     * Search location
17
     *
18
     * @var string
19
     */
20
    protected $location;
21
22
    /**
23
     * Maximum age of results (in days)
24
     *
25
     * @var integer
26
     */
27
    protected $maxAge;
28
29
    /**
30
     * Maximum number of results to return in all results
31
     *
32
     * @var integer
33
     */
34
    protected $maxResults;
35
36
    /**
37
     * Order of results
38
     *
39
     * @var string
40
     */
41
    protected $order = 'desc';
42
43
    /**
44
     * Field to order results by
45
     *
46
     * @var string
47
     */
48
    protected $orderBy = 'datePosted';
49
50
    /**
51
     * Results page number
52
     *
53
     * @var integer
54
     */
55
    protected $pageNumber;
56
57
    /**
58
     * Results per page
59
     *
60
     * @var integer
61
     */
62
    protected $perPage;
63
64
    /**
65
     * Job board API providers
66
     *
67
     * @var array
68
     */
69
    protected $providers = [];
70
71
    /**
72
     * Job board API query objects
73
     *
74
     * @var array
75
     */
76
    protected $queries = [];
77
78
    /**
79
     * Creates query objects for each provider and creates this unified client.
80
     *
81
     * @param array $providers
82
     */
83 12
    public function __construct($providers = [])
84
    {
85 12
        $this->setProviders($providers);
86 12
    }
87
88
    /**
89
     * Gets jobs from all providers in a single go and returns a MultiCollection
90
     *
91
     * @return MultiCollection
92
     */
93
    public function getAllJobs($options = [])
94
    {
95
        // Set options that are passed in
96
        $this->options = $this->setOptions($options);
0 ignored issues
show
Bug introduced by
The property options does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
97
98
        // Create a new MultiCollection
99
        $collection = new MultiCollection();
100
        foreach ($this->providers as $providerName => $options) {
101
            $collection->append($this->getJobsByProvider($providerName));
102
        }
103
104
        // Order the results
105
        $collection->orderBy($this->orderBy, $this->order);
106
107
        // Filter older listings out
108
        if ($this->maxAge) {
109
            $collection->filter(
110
                'datePosted',
111
                new \DateTime($this->maxAge.' days ago'),
0 ignored issues
show
Documentation introduced by
new \DateTime($this->maxAge . ' days ago') is of type object<DateTime>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
112
                '>'
113
            );
114
        }
115
116
        // Truncate to the maximum results (all by default)
117
        if ($this->maxResults) {
118
            $collection->truncate($this->maxResults);
119
        }
120
121
        return $collection;
122
    }
123
124
    /**
125
     * Gets jobs from a single provider and hydrates a new jobs collection.
126
     *
127
     * @var $name string Provider name.
128
     *
129
     * @return \JobApis\Jobs\Client\Collection
130
     */
131 2
    public function getJobsByProvider($name = null)
132
    {
133
        try {
134
            // Instantiate the query with all our parameters
135 2
            $query = $this->instantiateQuery($name);
136
137
            // Instantiate the provider
138
            $provider = $this->instantiateProvider($name, $query);
139
140
            // Get the jobs and return a collection
141
            return $provider->getJobs();
142 2
        } catch (\Exception $e) {
143 2
            return (new Collection())->addError($e->getMessage());
144
        }
145
    }
146
147
    /**
148
     * Sets a keyword on the query.
149
     *
150
     * @param $keyword string
151
     *
152
     * @return $this
153
     */
154 2
    public function setKeyword($keyword = null)
155
    {
156 2
        $this->keyword = $keyword;
157
158 2
        return $this;
159
    }
160
161
    /**
162
     * Sets a location on the query for each provider.
163
     *
164
     * @param $location
165
     *
166
     * @return $this
167
     */
168 4
    public function setLocation($location = null)
169
    {
170 4
        if (!$this->isValidLocation($location)) {
171 2
            throw new \OutOfBoundsException("Location parameter must follow the pattern 'City, ST'.");
172
        }
173 2
        $this->location = $location;
174
175 2
        return $this;
176
    }
177
178
    /**
179
     * Sets the options used for the resulting collection
180
     *
181
     * @param array $options
182
     *
183
     * @return $this
184
     */
185
    public function setOptions($options = [])
186
    {
187
        if (isset($options['maxAge'])) {
188
            $this->maxAge = $options['maxAge'];
189
        }
190
        if (isset($options['maxResults'])) {
191
            $this->maxResults = $options['maxResults'];
192
        }
193
        if (isset($options['order'])) {
194
            $this->order = $options['order'];
195
        }
196
        if (isset($options['orderBy'])) {
197
            $this->orderBy = $options['orderBy'];
198
        }
199
200
        return $this;
201
    }
202
203
    /**
204
     * Sets a page number and number of results per page for each provider.
205
     *
206
     * @param $pageNumber integer
207
     * @param $perPage integer
208
     *
209
     * @return $this
210
     */
211 2
    public function setPage($pageNumber = 1, $perPage = 10)
212
    {
213 2
        $this->pageNumber = $pageNumber;
214 2
        $this->perPage = $perPage;
215
216 2
        return $this;
217
    }
218
219
    /**
220
     * Sets an array of providers.
221
     *
222
     * @param $providers array
223
     *
224
     * @return $this
225
     */
226 12
    public function setProviders($providers = [])
227
    {
228 12
        $this->providers = $providers;
229
230 12
        return $this;
231
    }
232
233
    /**
234
     * Gets an array of options from a translator array
235
     *
236
     * @param array $translator
237
     *
238
     * @return array
239
     */
240
    protected function getOptionsFromTranslator($translator = [])
241
    {
242
        $options = [];
243
        foreach ($translator as $standardKey => $providerKey) {
244
            if (method_exists($this, $providerKey)) {
245
                $options = array_merge(
246
                    $options,
247
                    $this->$providerKey($this->{$standardKey})
248
                );
249
            } else {
250
                $options[$providerKey] = $this->{$standardKey};
251
            }
252
        }
253
        return $options;
254
    }
255
256
    /**
257
     * Gets the options array based on the provider name.
258
     *
259
     * @param $name
260
     *
261
     * @return array
262
     */
263
    protected function getTranslatorForProvider($name)
264
    {
265
        switch ($name) {
266
            case 'Careerbuilder':
267
                return [
268
                    'keyword' => 'Keywords',
269
                    'location' => 'Location',
270
                    'pageNumber' => 'PageNumber',
271
                    'perPage' => 'PerPage',
272
                ];
273
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
274
            case 'Careercast':
275
                return [
276
                    'keyword' => 'keyword',
277
                    'location' => 'location',
278
                    'pageNumber' => 'page',
279
                    'perPage' => 'rows',
280
                ];
281
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
282
            case 'Careerjet':
283
                return [
284
                    'keyword' => 'keywords',
285
                    'location' => 'location',
286
                    'pageNumber' => 'page',
287
                    'perPage' => 'pagesize',
288
                ];
289
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
290
            case 'Dice':
291
                return [
292
                    'keyword' => 'text',
293
                    'location' => 'getCityAndState',
294
                    'pageNumber' => 'page',
295
                    'perPage' => 'pgcnt',
296
                ];
297
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
298
            case 'Github':
299
                return [
300
                    'keyword' => 'search',
301
                    'location' => 'location',
302
                    'pageNumber' => 'getPageMinusOne',
303
                ];
304
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
305
            case 'Govt':
306
                return [
307
                    'keyword' => 'getQueryWithKeywordAndLocation',
308
                    'pageNumber' => 'getFrom',
309
                    'perPage' => 'size',
310
                ];
311
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
312
            case 'Ieee':
313
                return [
314
                    'keyword' => 'keyword',
315
                    'location' => 'location',
316
                    'pageNumber' => 'page',
317
                    'perPage' => 'rows',
318
                ];
319
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
320
            case 'Indeed':
321
                return [
322
                    'keyword' => 'q',
323
                    'location' => 'l',
324
                    'pageNumber' => 'getStart',
325
                    'perPage' => 'limit',
326
                ];
327
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
328
            case 'Jobinventory':
329
                return [
330
                    'keyword' => 'q',
331
                    'location' => 'l',
332
                    'pageNumber' => 'p',
333
                    'perPage' => 'limit',
334
                ];
335
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
336
            case 'Juju':
337
                return [
338
                    'keyword' => 'k',
339
                    'location' => 'l',
340
                    'pageNumber' => 'page',
341
                    'perPage' => 'jpp',
342
                ];
343
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
344
            case 'Stackoverflow':
345
                return [
346
                    'keyword' => 'q',
347
                    'location' => 'l',
348
                    'pageNumber' => 'pg',
349
                ];
350
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
351
            case 'Usajobs':
352
                return [
353
                    'keyword' => 'Keyword',
354
                    'location' => 'LocationName',
355
                    'pageNumber' => 'Page',
356
                    'perPage' => 'ResultsPerPage',
357
                ];
358
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
359
            case 'Ziprecruiter':
360
                return [
361
                    'keyword' => 'search',
362
                    'location' => 'location',
363
                    'pageNumber' => 'page',
364
                    'perPage' => 'jobs_per_page',
365
                ];
366
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
367
            default:
368
                throw new \Exception("Provider {$name} not found");
369
        }
370
    }
371
372
    /**
373
     * Instantiates a provider using a query object.
374
     *
375
     * @param null $name
376
     * @param AbstractQuery $query
377
     *
378
     * @return AbstractProvider
379
     */
380
    protected function instantiateProvider($name, AbstractQuery $query)
381
    {
382
        $path = 'JobApis\\Jobs\\Client\\Providers\\' . $name . 'Provider';
383
384
        return new $path($query);
385
    }
386
387
    /**
388
     * Instantiates a query using a client name.
389
     *
390
     * @param null $name
391
     *
392
     * @return AbstractQuery
393
     */
394 2
    protected function instantiateQuery($name)
395
    {
396 2
        $path = 'JobApis\\Jobs\\Client\\Queries\\' . $name . 'Query';
397
398 2
        $options = array_merge(
399 2
            $this->providers[$name],
400
            $this->getOptionsFromTranslator($this->getTranslatorForProvider($name))
0 ignored issues
show
Bug introduced by
It seems like $this->getTranslatorForProvider($name) targeting JobApis\Jobs\Client\Jobs...TranslatorForProvider() can also be of type null; however, JobApis\Jobs\Client\Jobs...OptionsFromTranslator() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
401
        );
402
403
        return new $path($options);
404
    }
405
406
    /**
407
     * Get the city and state as an array from a location string.
408
     *
409
     * @return array
410
     */
411
    private function getCityAndState()
412
    {
413
        if ($this->location) {
414
            $locationArr = explode(', ', $this->location);
415
            return [
416
                'city' => $locationArr[0],
417
                'state' => $locationArr[1],
418
            ];
419
        }
420
        return [];
421
    }
422
423
    /**
424
     * Gets a from value.
425
     *
426
     * @return array
427
     */
428
    private function getFrom()
429
    {
430
        if ($this->pageNumber && $this->perPage) {
431
            return [
432
                'from' => ($this->pageNumber * $this->perPage) - $this->perPage,
433
            ];
434
        }
435
        return [];
436
    }
437
438
    /**
439
     * Gets page number minus 1.
440
     *
441
     * @return array
442
     */
443
    private function getPageMinusOne()
444
    {
445
        if ($this->pageNumber) {
446
            return [
447
                'page' => $this->pageNumber - 1,
448
            ];
449
        }
450
        return [];
451
    }
452
453
    /**
454
     * Get the provider name from the method.
455
     *
456
     * @param $method
457
     *
458
     * @return string
459
     */
460
    private function getProviderFromMethod($method)
461
    {
462
        preg_match('/(get)(.*?)(Jobs)/', $method, $matches);
463
        return $matches[2];
464
    }
465
466
    /**
467
     * Get the query with keyword and location.
468
     *
469
     * @return array
470
     */
471
    private function getQueryWithKeywordAndLocation()
472
    {
473
        $queryString = $this->keyword;
474
475
        if ($this->location) {
476
            $queryString .= ' in '.$this->location;
477
        }
478
479
        return [
480
            'query' => $queryString,
481
        ];
482
    }
483
484
    /**
485
     * Gets a start at value.
486
     *
487
     * @return array
488
     */
489
    private function getStart()
490
    {
491
        if ($this->pageNumber && $this->perPage) {
492
            return [
493
                'start' => ($this->pageNumber * $this->perPage) - $this->perPage,
494
            ];
495
        }
496
        return [];
497
    }
498
499
    /**
500
     * Tests whether location string follows valid convention (City, ST).
501
     *
502
     * @param string $location
503
     *
504
     * @return bool
505
     */
506 4
    private function isValidLocation($location = null)
507
    {
508 4
        preg_match("/([^,]+),\s*(\w{2})/", $location, $matches);
509 4
        return isset($matches[1]) && isset($matches[2]) ? true : false;
510
    }
511
512
    /**
513
     * Tests whether the method is a valid get<Provider>Jobs() method.
514
     *
515
     * @param $method
516
     *
517
     * @return bool
518
     */
519
    private function isGetJobsByProviderMethod($method)
520
    {
521
        return preg_match('/(get)(.*?)(Jobs)/', $method, $matches) && $matches[2] && isset($this->queries[$matches[2]]);
522
    }
523
}
524