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