Model::getFileFields()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Trucker\Resource;
4
5
use Illuminate\Container\Container;
6
use Trucker\Facades\AuthFactory;
7
use Trucker\Facades\Collection;
8
use Trucker\Facades\Config;
9
use Trucker\Facades\ErrorHandlerFactory;
10
use Trucker\Facades\Instance;
11
use Trucker\Facades\RequestFactory;
12
use Trucker\Facades\ResponseInterpreterFactory;
13
use Trucker\Facades\UrlGenerator;
14
use Trucker\Finders\Conditions\QueryConditionInterface;
15
use Trucker\Finders\Conditions\QueryResultOrderInterface;
16
17
/**
18
 * Base class for interacting with a remote API.
19
 *
20
 * @author Alessandro Manno <[email protected]>
21
 */
22
class Model
23
{
24
    /**
25
     * The IoC Container.
26
     *
27
     * @var Container
28
     */
29
    protected $app;
30
31
    /**
32
     * The name of the resource which is used to determine
33
     * the resource URI through the use of reflection.  By default
34
     * if this is not set the class name will be used.
35
     *
36
     * @var string
37
     */
38
    protected $resourceName;
39
40
    /**
41
     * Property to overwrite the getURI()
42
     * function with a static value of what remote API URI path
43
     * to hit.
44
     *
45
     * @var string
46
     */
47
    protected $uri;
48
49
    /**
50
     * Property to hold the data about entities for which this
51
     * resource is nested beneath.  For example if this entity was
52
     * 'Employee' which was a nested resource under a 'Company' and
53
     * the instance URI should be /companies/:company_id/employees/:id
54
     * then you would assign this string with 'Company:company_id'.
55
     * Doing this will allow you to pass in ':company_id' as an option
56
     * to the URI creation functions and ':company_id' will be replaced
57
     * with the value passed.
58
     *
59
     * Alternativley you could set the value to something like 'Company:100'.
60
     * You could do this before a call like:
61
     *
62
     * <code>
63
     * $e = new Employee;
64
     * $e->nestedUnder = 'Company:100';
65
     * $found = Employee::find(1, [], $e);
66
     * //this would generate /companies/100/employees/1
67
     * </code>
68
     *
69
     *
70
     * This value can be nested as a comma separated string as well.
71
     * So you could set something like
72
     * "Company:company_id,Employee:employee_id,Preference:pref_id"
73
     * which would generate
74
     * /companies/:company_id/employees/:employee_id/preferences/:pref_id
75
     *
76
     * @var string
77
     */
78
    public $nestedUnder;
79
80
    /**
81
     * Array of instance values.
82
     *
83
     * @var array
84
     */
85
    protected $properties = [];
86
87
    /**
88
     * Remote resource's primary key property.
89
     *
90
     * @var string
91
     */
92
    protected $identityProperty;
93
94
    /**
95
     * Var to hold instance errors.
96
     *
97
     * @var array
98
     */
99
    protected $errors = [];
100
101
    /**
102
     * Comma separated list of properties that can't
103
     * be set via mass assignment.
104
     *
105
     * @var string
106
     */
107
    protected $guarded = '';
108
109
    /**
110
     * Comma separated list of properties that will take
111
     * a file path that should be read in and sent
112
     * with any API request.
113
     *
114
     * @var string
115
     */
116
    protected $fileFields = '';
117
118
    /**
119
     * Comma separated list of properties that may be in
120
     * a GET request but should not be added to a create or
121
     * update request.
122
     *
123
     * @var string
124
     */
125
    protected $readOnlyFields = '';
126
127
    /**
128
     * Array of files that were temporarily written for a request
129
     * that should be removed after the request is done.
130
     *
131
     * @var array
132
     */
133
    private $postRequestCleanUp = [];
134
135
    /**
136
     * Filesystem location that temporary files could be
137
     * written to if needed.
138
     *
139
     * @var string
140
     */
141
    protected $scratchDiskLocation;
142
143
    /**
144
     * Portion of a property name that would indicate
145
     * that the value would be Base64 encoded when the
146
     * property is set.
147
     *
148
     * @var string
149
     */
150
    protected $base64Indicator;
151
152
    /**
153
     * Constructor used to popuplate the instance with
154
     * attribute values.
155
     *
156
     * @param array $attributes Associative array of property names and values
157
     */
158 42
    public function __construct(array $attributes = [])
159
    {
160 42
        $this->fill($attributes);
161 42
    }
162
163
    /**
164
     * Create a new instance of the given model.
165
     *
166
     * @param array $attributes
167
     *
168
     * @return \Trucker\Resource\Model
169
     */
170 13
    public function newInstance(array $attributes = [])
171
    {
172
        // This method just provides a convenient way for us to generate fresh model
173
        // instances of this current model. It is particularly useful during the
174
        // hydration of new objects.
175 13
        $model = new static();
176
177 13
        $model->fill((array) $attributes);
178
179 13
        return $model;
180
    }
181
182
    /**
183
     * Magic getter function for accessing instance properties.
184
     *
185
     * @param string $key Property name
186
     *
187
     * @return mixed The value stored in the property
188
     */
189 14
    public function __get($key)
190
    {
191 14
        if (array_key_exists($key, $this->properties)) {
192 14
            return $this->properties[$key];
193
        }
194
195 1
        return null;
196
    }
197
198
    /**
199
     * Magic setter function for setting instance properties.
200
     *
201
     * @param string $property Property name
202
     * @param mixed  $value    The value to store for the property
203
     */
204 30
    public function __set($property, $value)
205
    {
206
        //if property contains '_base64'
207 30
        if (!(false === stripos($property, $this->getBase64Indicator()))) {
208
            //if the property IS a file field
209 1
            $fileProperty = str_replace($this->getBase64Indicator(), '', $property);
210 1
            if (in_array($fileProperty, $this->getFileFields(), true)) {
211 1
                $this->handleBase64File($fileProperty, $value);
212
            }//end if file field
213
        } else {
214 30
            $this->properties[$property] = $value;
215
        }
216 30
    }
217
218
    //end __set
219
220
    /**
221
     * Magic unsetter function for unsetting an instance property.
222
     *
223
     * @param string $property Property name
224
     */
225 1
    public function __unset($property)
226
    {
227 1
        if (array_key_exists($property, $this->properties)) {
228 1
            unset($this->properties[$property]);
229
        }
230 1
    }
231
232
    //end __unset
233
234
    /**
235
     * Getter function to access the
236
     * underlying attributes array for the
237
     * entity.
238
     *
239
     * @return array
240
     */
241 15
    public function attributes()
242
    {
243 15
        return $this->properties;
244
    }
245
246
    /**
247
     * Function to return any errors that
248
     * may have prevented a save.
249
     *
250
     * @return array
251
     */
252 7
    public function errors()
253
    {
254 7
        return $this->errors;
255
    }
256
257
    /**
258
     * Function to fill an instance's properties from an
259
     * array of keys and values.
260
     *
261
     * @param array $attributes Associative array of properties and values
262
     */
263 42
    public function fill(array $attributes = [])
264
    {
265 42
        $guarded = $this->getGuardedAttributes();
266
267 42
        foreach ($attributes as $property => $value) {
268 19
            if (!in_array($property, $guarded, true)) {
269
                //get the fields on the entity that are files
270 19
                $fileFields = $this->getFileFields();
271
272
                //if property contains base64 indicator
273 19
                if (!(false === stripos($property, $this->getBase64Indicator()))) {
274
                    //get a list of file properties w/o the base64 indicator
275 1
                    $fileProperty = str_replace($this->getBase64Indicator(), '', $property);
276
277
                    //if the property IS a file field, handle appropriatley
278 1
                    if (in_array($fileProperty, $fileFields, true)) {
279 1
                        $this->handleBase64File($fileProperty, $value);
280
                    }//end if file field
281
                } else {
282
                    //handle as normal property, but file fields can't be mass assigned
283 18
                    if (!in_array($property, $fileFields, true)) {
284 19
                        $this->properties[$property] = $value;
285
                    }
286
                }//end if-else base64
287
            }//end if not guarded
288
        }//end foreach
289 42
    }
290
291
    /**
292
     * Function to return an array of properties that should not
293
     * be set via mass assignment.
294
     *
295
     * @return array
296
     */
297 42
    public function getGuardedAttributes()
298
    {
299 42
        $attrs = array_map('trim', explode(',', $this->guarded));
300
301
        //the identityProperty should always be guarded
302 42
        if (!in_array($this->getIdentityProperty(), $attrs, true)) {
303 42
            $attrs[] = $this->getIdentityProperty();
304
        }
305
306 42
        return $attrs;
307
    }
308
309
    /**
310
     * Function to return an array of properties that will
311
     * accept a file path.
312
     *
313
     * @return array
314
     */
315 20
    public function getFileFields()
316
    {
317 20
        $attrs = array_map('trim', explode(',', $this->fileFields));
318
319 20
        return array_filter($attrs);
320
    }
321
322
    /**
323
     * Function to take base64 encoded image and write it to a
324
     * temp file, then add that file to the property list to get
325
     * added to a request.
326
     *
327
     * @param string $property Entity attribute
328
     * @param string $value    Base64 encoded string
329
     */
330 1
    protected function handleBase64File($property, $value)
331
    {
332 1
        $image = base64_decode($value);
333 1
        $imgData = \getimagesizefromstring($image);
0 ignored issues
show
Bug introduced by
It seems like $image can also be of type false; however, parameter $imagedata of getimagesizefromstring() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

333
        $imgData = \getimagesizefromstring(/** @scrutinizer ignore-type */ $image);
Loading history...
334 1
        $mimeExp = explode('/', $imgData['mime']);
335 1
        $ext = end($mimeExp);
336 1
        $output_file = implode(
337 1
            DIRECTORY_SEPARATOR,
338 1
            [$this->getScratchDiskLocation(), uniqid("tmp_{$property}_", true) . ".$ext"]
339
        );
340 1
        $f = fopen($output_file, 'wb');
341 1
        fwrite($f, $image);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
        fwrite(/** @scrutinizer ignore-type */ $f, $image);
Loading history...
Bug introduced by
It seems like $image can also be of type false; however, parameter $string of fwrite() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

341
        fwrite($f, /** @scrutinizer ignore-type */ $image);
Loading history...
342 1
        fclose($f);
0 ignored issues
show
Bug introduced by
It seems like $f can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

342
        fclose(/** @scrutinizer ignore-type */ $f);
Loading history...
343
344 1
        $this->postRequestCleanUp[] = $output_file;
345 1
        $this->{$property} = $output_file;
346 1
    }
347
348
    //end handleBase64File
349
350
    /**
351
     * Function to get the instance ID, returns false if there
352
     * is not one.
353
     *
354
     * @return mixed
355
     */
356 13
    public function getId()
357
    {
358 13
        if (array_key_exists($this->getIdentityProperty(), $this->properties)) {
359 11
            return $this->properties[$this->getIdentityProperty()];
360
        }
361
362 4
        return false;
363
    }
364
365
    /**
366
     * Getter function to return the identity property.
367
     *
368
     * @return string
369
     */
370 42
    public function getIdentityProperty()
371
    {
372 42
        return $this->identityProperty ?: Config::get('resource.identity_property');
0 ignored issues
show
Bug introduced by
The method get() does not exist on Trucker\Facades\Config. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

372
        return $this->identityProperty ?: Config::/** @scrutinizer ignore-call */ get('resource.identity_property');
Loading history...
373
    }
374
375
    /**
376
     * Getter function to return the scratch disk location.
377
     *
378
     * @return string
379
     */
380 1
    public function getScratchDiskLocation()
381
    {
382 1
        return $this->scratchDiskLocation ?: Config::get('resource.scratch_disk_location');
383
    }
384
385
    /**
386
     * Getter function to return base64 param indicator.
387
     *
388
     * @return string
389
     */
390 26
    public function getBase64Indicator()
391
    {
392 26
        return $this->base64Indicator ?: Config::get('resource.base_64_property_indication');
393
    }
394
395
    /**
396
     * Function to return an array of property names
397
     * that are read only.
398
     *
399
     * @return array
400
     */
401 10
    public function getReadOnlyFields()
402
    {
403 10
        $cantSet = array_map('trim', explode(',', $this->readOnlyFields));
404
405 10
        return $cantSet;
406
    }
407
408
    /**
409
     * Function to get an associative array of fields
410
     * with their values that are NOT read only.
411
     *
412
     * @return array
413
     */
414 1
    public function getMutableFields()
415
    {
416 1
        $cantSet = $this->getReadOnlyFields();
417
418 1
        $mutableFields = [];
419
420
        //set the property attributes
421 1
        foreach ($this->properties as $key => $value) {
422 1
            if (!in_array($key, $cantSet, true)) {
423 1
                $mutableFields[$key] = $value;
424
            }
425
        }
426
427 1
        return $mutableFields;
428
    }
429
430
    /**
431
     * Function to interpret the URI resource name based on the class called.
432
     * Generally this would be the name of the class.
433
     *
434
     * @return string The sub name of the resource
435
     */
436 26
    public function getResourceName()
437
    {
438 26
        if (isset($this->resourceName)) {
439 5
            return $this->resourceName;
440
        }
441
442 26
        $full_class_arr = explode('\\', get_called_class());
443 26
        $klass = end($full_class_arr);
444 26
        $this->resourceName = $klass;
445
446 26
        return $klass;
447
    }
448
449
    /**
450
     * Getter function to return a URI
451
     * that has been manually set.
452
     *
453
     * @return string
454
     */
455 25
    public function getURI()
456
    {
457 25
        return $this->uri ?: null;
458
    }
459
460
    /**
461
     * Function to find an instance of an Entity record.
462
     *
463
     * @param int   $id        The primary identifier value for the record
464
     * @param array $getParams Array of GET parameters to pass
465
     * @param Model $instance  An instance to use for interpreting url values
466
     *
467
     * @return Model An instance of the entity requested
468
     */
469 2
    public static function find($id, array $getParams = [], self $instance = null)
470
    {
471 2
        $m = $instance ?: new static();
472
473 2
        return Instance::fetch($m, $id, $getParams);
0 ignored issues
show
Bug introduced by
The method fetch() does not exist on Trucker\Facades\Instance. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

473
        return Instance::/** @scrutinizer ignore-call */ fetch($m, $id, $getParams);
Loading history...
474
    }
475
476
    /**
477
     * Function to find a collection of Entity records from the remote api.
478
     *
479
     * @param QueryConditionInterface   $condition   query conditions
480
     * @param QueryResultOrderInterface $resultOrder result ordering info
481
     * @param array                     $getParams   additional GET params
482
     *
483
     * @return Collection
484
     */
485 4
    public static function all(
486
        QueryConditionInterface $condition = null,
487
        QueryResultOrderInterface $resultOrder = null,
488
        array $getParams = []
489
    ) {
490 4
        return Collection::fetch(new static(), $condition, $resultOrder, $getParams);
0 ignored issues
show
Bug introduced by
The method fetch() does not exist on Trucker\Facades\Collection. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

490
        return Collection::/** @scrutinizer ignore-call */ fetch(new static(), $condition, $resultOrder, $getParams);
Loading history...
491
    }
492
493
    /**
494
     * Function to handle persistance of the entity across the
495
     * remote API.  Function will handle either a CREATE or UPDATE.
496
     *
497
     * @return bool Success of the save operation
498
     */
499 8
    public function save()
500
    {
501
        //get a request object
502 8
        $request = RequestFactory::build();
0 ignored issues
show
Bug introduced by
The method build() does not exist on Trucker\Facades\RequestFactory. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

502
        /** @scrutinizer ignore-call */ 
503
        $request = RequestFactory::build();
Loading history...
503
504 8
        if (false === $this->getId()) {
505
            //make a CREATE request
506 4
            $request->createRequest(
507 4
                Config::get('request.base_uri'),
508 4
                UrlGenerator::getCreateUri($this),
0 ignored issues
show
Bug introduced by
The method getCreateUri() does not exist on Trucker\Facades\UrlGenerator. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

508
                UrlGenerator::/** @scrutinizer ignore-call */ 
509
                              getCreateUri($this),
Loading history...
509 4
                'POST',
510 4
                [], //no extra headers
511 4
                Config::get('request.http_method_param')
512
            );
513
        } else {
514
            //make an UPDATE request
515 4
            $request->createRequest(
516 4
                Config::get('request.base_uri'),
517 4
                UrlGenerator::getDeleteUri(
0 ignored issues
show
Bug introduced by
The method getDeleteUri() does not exist on Trucker\Facades\UrlGenerator. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

517
                UrlGenerator::/** @scrutinizer ignore-call */ 
518
                              getDeleteUri(
Loading history...
518 4
                    $this,
519 4
                    [':' . $this->getIdentityProperty() => $this->getId()]
520
                ),
521 4
                'PUT',
522 4
                [], //no extra headers
523 4
                Config::get('request.http_method_param')
524
            );
525
        }
526
527
        //add auth if it is needed
528 8
        if ($auth = AuthFactory::build()) {
0 ignored issues
show
Bug introduced by
The method build() does not exist on Trucker\Facades\AuthFactory. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

528
        if ($auth = AuthFactory::/** @scrutinizer ignore-call */ build()) {
Loading history...
529
            $request->authenticate($auth);
530
        }
531
532
        //set the property attributes on the request
533 8
        $request->setModelProperties($this);
534
535
        //actually send the request
536 8
        $response = $request->sendRequest();
537
538
        //handle clean response with errors
539 8
        if (ResponseInterpreterFactory::build()->invalid($response)) {
0 ignored issues
show
Bug introduced by
The method build() does not exist on Trucker\Facades\ResponseInterpreterFactory. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

539
        if (ResponseInterpreterFactory::/** @scrutinizer ignore-call */ build()->invalid($response)) {
Loading history...
540
            //get the errors and set them to our local collection
541 4
            $this->errors = ErrorHandlerFactory::build()->parseErrors($response);
0 ignored issues
show
Bug introduced by
The method build() does not exist on Trucker\Facades\ErrorHandlerFactory. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

541
            $this->errors = ErrorHandlerFactory::/** @scrutinizer ignore-call */ build()->parseErrors($response);
Loading history...
542
543
            //do any needed cleanup
544 4
            $this->doPostRequestCleanUp();
545
546 4
            return false;
547
        }//end if
548
549
        //get the response and inflate from that
550 4
        $data = $response->parseResponseToData();
551 4
        $this->fill($data);
552
553
        //inflate the ID property that should be guarded
554
        //and thus not fillable
555 4
        $id = $this->getIdentityProperty();
556 4
        if (array_key_exists($id, $data)) {
557 4
            $this->{$id} = $data[$id];
558
        }
559
560 4
        $this->doPostRequestCleanUp();
561
562 4
        return true;
563
    }
564
565
    /**
566
     * Function to delete an existing entity.
567
     *
568
     * @return bool Success of the delete operation
569
     */
570 4
    public function destroy()
571
    {
572
        //get a request object
573 4
        $request = RequestFactory::build();
574
575
        //init the request
576 4
        $request->createRequest(
577 4
            Config::get('request.base_uri'),
578 4
            UrlGenerator::getDeleteUri(
579 4
                $this,
580 4
                [':' . $this->getIdentityProperty() => $this->getId()]
581
            ),
582 4
            'DELETE',
583 4
            [], //no extra headers
584 4
            Config::get('request.http_method_param')
585
        );
586
587
        //add auth if it is needed
588 4
        if ($auth = AuthFactory::build()) {
589
            $request->authenticate($auth);
590
        }
591
592
        //actually send the request
593 4
        $response = $request->sendRequest();
594
595
        //clean up anything no longer needed
596 4
        $this->doPostRequestCleanUp();
597
598 4
        $interpreter = ResponseInterpreterFactory::build();
599
600
        //handle clean response with errors
601 4
        if ($interpreter->success($response)) {
602 2
            return true;
603
        }
604
605 2
        if ($interpreter->invalid($response)) {
606
            //get the errors and set them to our local collection
607 2
            $this->errors = ErrorHandlerFactory::build()->parseErrors($response);
608
        }//end if-else
609
610 2
        return false;
611
    }
612
613
    /**
614
     * Function to clean up any temp files written for a request.
615
     */
616 13
    protected function doPostRequestCleanUp()
617
    {
618 13
        while (count($this->postRequestCleanUp) > 0) {
619 1
            $f = array_pop($this->postRequestCleanUp);
620 1
            if (file_exists($f)) {
621 1
                unlink($f);
622
            }
623
        }
624 13
    }
625
}
626