Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
73.33% covered (warning)
73.33%
11 / 15
CRAP
94.59% covered (success)
94.59%
105 / 111
Customer
0.00% covered (danger)
0.00%
0 / 1
73.33% covered (warning)
73.33%
11 / 15
35.19
94.59% covered (success)
94.59%
105 / 111
 __construct
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
3 / 3
 __get
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
7 / 7
 __set
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
5 / 5
 fill
0.00% covered (danger)
0.00%
0 / 1
6.40
77.78% covered (warning)
77.78%
7 / 9
 validateAllValues
0.00% covered (danger)
0.00%
0 / 1
3.21
71.43% covered (warning)
71.43%
5 / 7
 find
0.00% covered (danger)
0.00%
0 / 1
3.01
91.67% covered (success)
91.67%
11 / 12
 findWithEmail
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 save
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
12 / 12
 store
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
9 / 9
 update
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
10 / 10
 delete
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
6 / 6
 activate
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 toggleOptin
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 login
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 jsonSerialize
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
14 / 14
1<?php
2
3namespace LDC\LDCCRM\Models;
4
5use LDC\LDCCRM\Enums\ApiError;
6use LDC\LDCCRM\Enums\ApiStatus;
7use LDC\LDCCRM\Enums\Country;
8use LDC\LDCCRM\Exceptions\ApiErrorException;
9use LDC\LDCCRM\Utils;
10use TypeError;
11
12/**
13 * This class represents the customer info with
14 * the account validation status. You can use it as
15 * a standalone class but beware of catching exceptions
16 *
17 * Be warned that on server side data is normalized
18 * - firstname and lastname are 'capitalized'
19 * - fields are trimmed
20 * - non numeric chars are removed from postcode
21 *
22 * @property string $hashcode The crm hashcode this is a readonly field
23 * @property string $email Must be a rfc valid email on a valid domain name
24 * @property string $password Required only on create, must have upper and lowers chars,
25 * at least 1 number, at leat 1 upper char, at least 1 lower char and
26 * at leat 1 special char
27 * @property string $firstname The account first name
28 * @property string $lastname The account last name
29 * @property string $birth_date The account birth date 'yyyy-mm-dd'
30 * @property string $phone The account phone
31 * @property string $address The account address (street number, name, build, flat number)
32 * @property string $address2 The account phone (address complement)
33 * @property string $city The account city name
34 * @property string $postcode The account city postcode
35 * @property \LDC\LDCCRM\Enums\Country $country The account country residence
36 * @property bool $opt_in true if the account is opt in
37 * @property bool $status true if the account is activated
38 * @property string $last_connection the last time user was connected 'yyyy-mm-dd HH:ii:ss'
39 *
40 * @example examples/CustomerExample.php
41 *
42 * @category Api Models
43 * @package LDCCrmClient
44 * @subpackage models
45 * @author Jérémy Munsch <jeremy.munsch@ldc.fr>
46 */
47class Customer extends CrmClientModel
48{
49    /**
50     * The user hashcode, this is link with the customer and the brand it is related to
51     * It is used to identify the customer itself.
52     *
53     * Validate: UUID, in customer_infos
54     *
55     * Required
56     * Min length 36
57     * Max length 36
58     * Field Type varchar
59     *
60     * @var string
61     */
62    private $hashcode;
63
64    /**
65     * An RFC Valid Email
66     *
67     * Validate: rfc,dns,spoof,filter
68     *
69     * Required
70     * Min length N/A
71     * Max length 255
72     * Field Type varchar
73     *
74     * @var string
75     */
76    private $email = null;
77
78    /**
79     * The user password
80     * This is set only when creating a user
81     * Otherwise it has to be set to null
82     *
83     * Validate: case_diff,numbers,letters,symbols,min:8
84     *
85     * Min length 8
86     * Max length 255
87     * Field Type varchar
88     *
89     * @var string
90     */
91    private $password = null;
92
93    /**
94     * First name
95     *
96     * Required
97     * Min length 2
98     * Max length 255
99     * Field Type varchar
100     *
101     * @var string
102     */
103    private $firstname = null;
104
105    /**
106     * Last name
107     *
108     * Required
109     * Min length 2
110     * Max length 255
111     * Field Type varchar
112     *
113     * @var string
114     */
115    private $lastname = null;
116
117    /**
118     * Birth date
119     *
120     * Validate: date_format:Y-m-d
121     *
122     * Required
123     * Field Type Date
124     * @var string
125     */
126    private $birth_date = null;
127
128    /**
129     * The user phone
130     *
131     * Validate: sometimes,string
132     *
133     * Required
134     * Min length NA
135     * Max length 255
136     * Field Type varchar
137     *
138     * @var string
139     */
140    private $phone = null;
141
142    /**
143     * The user address (street number, name, etc)
144     *
145     * Validate: sometimes,string
146     *
147     * Min length NA
148     * Max length 255
149     * Field Type varchar
150     *
151     * @var string
152     */
153    private $address = null;
154
155    /**
156     * The user address 2nd part (street number, name, more, etc)
157     *
158     * Validate: sometimes,string
159     *
160     * Min length NA
161     * Max length 255
162     * Field Type varchar
163     *
164     * @var string
165     */
166    private $address2 = null;
167
168    /**
169     * The user address city
170     *
171     * Validate: sometimes,string
172     *
173     * Min length NA
174     * Max length 255
175     * Field Type varchar
176     *
177     * @var string
178     */
179    private $city = null;
180
181    /**
182     * The user address postcode
183     *
184     * Validate: string
185     *
186     * Required
187     * Min length NA
188     * Max length 255
189     * Field Type varchar
190     *
191     * @var string
192     */
193    private $postcode = null;
194
195    /**
196     * The user address postcode
197     *
198     * Validate: sometimes, in Country::toValues()
199     *
200     * Required
201     * Field Type tinyInt
202     *
203     * @var \LDC\LDCCRM\Enums\Country
204     */
205    private $country = null;
206
207    /**
208     * The user optin choice
209     *
210     * Validate: boolean
211     *
212     * Required
213     * Field Type TinyInt
214     *
215     * @var boolean
216     */
217    private $opt_in = false;
218
219    /**
220     * Is the account activated ?
221     *
222     * This field is read only
223     *
224     * @var boolean
225     */
226    private $status = false;
227
228    /**
229     * The last time the account was connected
230     *
231     * This field is read only
232     *
233     * @var string
234     */
235    private $last_connection = null;
236
237    /**
238     * The crm customer fields
239     *
240     * @var array
241     */
242    protected $modelFields = [
243        'hashcode', 'email', 'firstname', 'password',
244        'lastname', 'birth_date', 'phone', 'address',
245        'address2', 'city', 'postcode', 'country',
246        'opt_in', 'status', 'last_connection'
247    ];
248
249    /**
250     * The crm customer fields
251     * the should not be visible
252     *
253     * @var array
254     */
255    protected $invisibleFields = [
256        'password'
257    ];
258
259    /**
260     * Validation rules
261     *
262     * @var array
263     */
264    protected static $validation = [
265        'hashcode' => 'required|uuid',
266        'email' => 'required|email',
267        'firstname' => 'required|string|min:1|max:255',
268        'lastname' => 'required|string|min:1|max:255',
269        'password' => 'required|min:8|max:255|mixedCase|containDigit|containSymbol',
270        'birth_date' => 'required|date_format:Y-m-d',
271        'phone' => 'sometimes|nullable|string|max:255',
272        'address' => 'sometimes|string|max:255',
273        'address2' => 'sometimes|string|max:255',
274        'city' => 'sometimes|string|min:2|max:255',
275        'postcode' => 'sometimes|string|max:255',
276        'country' => ['sometimes'],
277        'opt_in' => 'required|bool',
278        'status' => 'required|bool',
279        'last_connection' => 'sometimes|date'
280    ];
281
282    /**
283     * Customer
284     *
285     * @param array $properties
286     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
287     */
288    public function __construct(array $properties = [])
289    {
290        parent::__construct();
291        $this->fill($properties);
292        return $this;
293    }
294
295    /**
296     * Generic accessor
297     *
298     * @param string $field
299     * @return mixed|null Returns null if the property was not found any way.
300     */
301    public function __get(string $field)
302    {
303        $props = \array_diff(
304            $this->modelFields,
305            $this->invisibleFields
306        );
307        if (in_array($field, $props)) {
308            if ($field === 'country' and !\is_object($this->{$field})) {
309                return Country::make($this->{$field});
310            }
311        }
312        return parent::__get($field);
313    }
314
315    /**
316     * Set prop on the model.
317     *
318     * @param string $field
319     * @param mixed  $value
320     * @return void
321     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
322     * @throws \LDC\LDCCRM\Exceptions\AccessError If trying to set a value on a unknown property.
323     */
324    public function __set(string $field, $value): void
325    {
326        if ($field === 'country') {
327            $value = (\is_object($value) and
328                get_class($value) === Country::class) ? $value->value : $value;
329        }
330        parent::__set($field, $value);
331    }
332
333    /**
334     * Fill values into model using validation rules.
335     *
336     * @param array $properties
337     * @return void
338     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
339     */
340    public function fill(array $properties)
341    {
342        if (isset($properties['country'])) {
343            try {
344                $properties['country'] = (\is_object($properties['country']) and
345                    get_class($properties['country']) === Country::class) ?
346                    $properties['country']->value : (ctype_digit($properties['country']) ?
347                        (int)$properties['country'] : $properties['country']);
348            } catch (TypeError | \BadMethodCallException $e) {
349                // * Cast anyway
350                $properties['country'] = (int)$properties['country'];
351            }
352        }
353        parent::fill($properties);
354    }
355
356    /**
357     * Validate all model properties
358     *
359     * @param array $ignore
360     * @return array the validated values
361     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
362     */
363    protected function validateAllValues(array $ignore = []): array
364    {
365        $out = [];
366        if (!\in_array('country', $ignore)) {
367            try {
368                $this->validateValue('country', Country::make(Utils::getRawProperty($this, 'country')));
369            } catch (\TypeError | \BadMethodCallException $e) {
370                // Use this to throw validation error.
371                $this->validateValue('country', Utils::getRawProperty($this, 'country'));
372            }
373            $out['country'] = Utils::getRawProperty($this, 'country');
374        }
375        return array_merge($out, parent::validateAllValues(array_merge(['country'], $ignore)));
376    }
377
378    // * PUBLIC METHODS
379
380    /**
381     * Find a customer using its hashcode in the crm
382     *
383     * @param string $hashcode
384     * @return self|null Return null if the customer was not found
385     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
386     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
387     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
388     */
389    public static function find(string $hashcode): ?self
390    {
391        static::setUpApiInterface();
392        $request  = Utils::getResquest('apiV2.customers.show', [
393            'serviceCustomer' => $hashcode
394        ]);
395        $customer = null;
396        try {
397            $response = static::$apiInterface->call($request);
398            $customer = new self();
399            // ! Use fillRaw here because we cant validate comming
400            // ! from CRM since it has crappy data comming from the old crm (jellix).
401            $customer->fillRaw($response->data);
402            Utils::setRawProperty($customer, 'isNew', false);
403        } catch (ApiErrorException $e) {
404            if (!$e->getApiError()->equals(ApiError::modelNotFound())) {
405                throw $e;
406            }
407        }
408        return $customer;
409    }
410
411    /**
412     * Find a customer using its email in the crm
413     * This is an alias of Customer::find()
414     *
415     * @param string $email
416     * @return self|null
417     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
418     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
419     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
420     */
421    public static function findWithEmail(string $email): ?self
422    {
423        return static::find($email);
424    }
425
426    /**
427     * Save the customer on CRM.
428     *
429     * @return boolean True if save was successfull.
430     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
431     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
432     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
433     * Possibles are : - validationFailed, If provided data is wrong(CRM side).
434     *                 - modelNotFound, If this customer hashcode could not be found (new customers only).
435     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
436     */
437    public function save(): bool
438    {
439        $saved = false;
440        if ($this->isNew) {
441            $saved               = $this->store();
442            $this->isNew         = !$saved;
443            $this->updatedValues = [];
444            if ($saved) {
445                Utils::setRawProperty($this, 'password', null);
446            }
447            return $saved;
448        }
449        if ($saved = $this->update()) {
450            $this->updatedValues = [];
451            Utils::setRawProperty($this, 'password', null);
452        }
453        return $saved;
454    }
455
456    /**
457     * Calls api store customer method
458     *
459     * @return boolean True if save was successfull.
460     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
461     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
462     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
463     * Possibles are : - validationFailed, If provided data is wrong(CRM side).
464     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
465     */
466    private function store(): bool
467    {
468        $data             = $this->validateAllValues(['hashcode', 'last_connection', 'status']);
469        $data['password'] = $this->password;
470
471        static::setUpApiInterface();
472        $request = Utils::getResquest('apiV2.customers.store', [], $data);
473
474        $response = static::$apiInterface->call($request);
475        if ($status = $response->status->equals(ApiStatus::OK())) {
476            $this->updatedValues = [];
477            $this->fill($response->data);
478        }
479        return $status;
480    }
481
482    /**
483     * Calls api update customer method
484     *
485     * @return boolean True if save was successfull.
486     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
487     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
488     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
489     * Possibles are : - validationFailed, If provided data is wrong(CRM side).
490     *                 - modelNotFound, If this customer hashcode could not be found.
491     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
492     */
493    private function update(): bool
494    {
495        $this->validateValue('hashcode', $this->hashcode);
496        static::setUpApiInterface();
497        $request = Utils::getResquest('apiV2.customers.update', [
498            'serviceCustomer' => $this->hashcode
499        ], $this->updatedValues);
500
501        $response = static::$apiInterface->call($request);
502        if ($status = $response->status->equals(ApiStatus::OK())) {
503            $this->updatedValues = [];
504            $this->fill($response->data);
505        }
506        return $status;
507    }
508
509    /**
510     * Removes the customer from CRM.
511     *
512     * @return boolean true if succeeded
513     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
514     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
515     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
516     * Possibles are : - modelNotFound, If this customer hashcode could not be found.
517     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
518     */
519    public function delete(): bool
520    {
521        $this->validateValue('hashcode', $this->hashcode);
522        static::setUpApiInterface();
523        $request = Utils::getResquest('apiV2.customers.destroy', [
524            'serviceCustomer' => $this->hashcode
525        ]);
526
527        $response = static::$apiInterface->call($request);
528        return $response->status->equals(ApiStatus::OK());
529    }
530
531    /**
532     * Active the customer on CRM.
533     *
534     * @return boolean true if succeeded
535     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a customer's hashcode could not be validated.
536     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
537     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
538     * Possibles are : - modelNotFound, If this customer hashcode could not be found.
539     *                 - customerAlreadyActive, It the customer is already activated.
540     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
541     */
542    public function activate(): bool
543    {
544        $this->validateValue('hashcode', $this->hashcode);
545        static::setUpApiInterface();
546        $request = Utils::getResquest('apiV2.customers.activate', [
547            'serviceCustomer' => $this->hashcode
548        ]);
549
550        $response = static::$apiInterface->call($request);
551        $this->__set('status', true);
552        return $response->status->equals(ApiStatus::OK());
553    }
554
555    /**
556     * Toggle the account optin value in the crm
557     *
558     * @return boolean true if succeeded
559     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a field could not be validated.
560     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
561     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
562     * Possibles are : - modelNotFound, If this customer hashcode could not be found.
563     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
564     */
565    public function toggleOptin(): bool
566    {
567        $this->__set('opt_in', !$this->opt_in);
568        return $this->update();
569    }
570
571    /**
572     * Test customer credentials
573     *
574     * @param string $email
575     * @param string $password
576     * @return boolean
577     * @throws \LDC\LDCCRM\Exceptions\ValidationException If a customer's email could not be validated.
578     * @throws \LDC\LDCCRM\Exceptions\CrmException If an error occurs on server side or on getting response.
579     * @throws \LDC\LDCCRM\Exceptions\ApiErrorException If any error or \LDC\LDCCRM\Enums\ApiError occurs.
580     * Possibles are : - authFailed, If credentials are not valid.
581     *                 - customerNotActivated, If the account is not activated.
582     * @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
583     */
584    public static function login(string $email, string $password): bool
585    {
586        self::validateValue('email', $email);
587        static::setUpApiInterface();
588        $request  = Utils::getResquest('apiV2.customers.authenticate', [], [
589            'email' => $email,
590            'password' => $password
591        ]);
592        $response = static::$apiInterface->call($request);
593        return $response->status->equals(ApiStatus::OK());
594    }
595
596    /**
597     * Serialize this object to an array
598     *
599     * @return array
600     */
601    public function jsonSerialize()
602    {
603        return [
604            'hashcode' => $this->hashcode,
605            'email' => $this->email,
606            'firstname' => $this->firstname,
607            'lastname' => $this->lastname,
608            'birth_date' => $this->birth_date,
609            'phone' => $this->phone,
610            'address' => $this->address,
611            'address2' => $this->address2,
612            'city' => $this->city,
613            'postcode' => $this->postcode,
614            'country' => $this->country,
615            'opt_in' => $this->opt_in,
616            'status' => $this->status,
617            'last_connection' => $this->last_connection
618        ];
619    }
620}