*/ protected $casts = [ 'type' => DepartaimantCollectionType::class, ]; /** * The table associated with the model. * * @var string */ protected $table = 'departaimants'; /** * The model's attributes. * * @var array */ public $attributes = [ 'type' => DepartaimantCollectionType::current, ]; /** * The attributes that are mass assignable. * * @var array */ public $fillable = [ 'type', ]; /** * Columns prefix. * * @var string */ private static $columnTableNamePrefix = 'col'; /** * Le prefix d'un table departaimant. * * @var int */ private static $tableFields = 4; /** * Le nombre de colonnes des departaimants. * * * La largeur en bits d'un départaimant * * to be set in construct * * @var int */ private static $departementWidth = 2; /** * Handle dynamic static method calls into the model. * * @param string $name * @param array $arguments * @return mixed * * @phpcs:disable Squiz.Commenting.FunctionComment.TypeHintMissing * * @phpcs:disable Squiz.Commenting.FunctionComment.ScalarTypeHintMissing */ public static function __callStatic($name, $arguments) { // phpcs:enable if ($name === 'defaultType') { return DepartaimantCollectionType::current; } return parent::__callStatic($name, $arguments); } /** * Create a new Eloquent model instance. */ public function __construct(array $attributes = []) { $this->type = DepartaimantCollectionType::current; // Set Default 0 to cols instead of null. for ($i = 1; $i < $this->tableFields; $i++) { $this->attributes[sprintf('col%d', $i)] = 0; } if (\array_key_exists('type', $attributes)) { $this->attributes['type'] = DepartaimantCollectionType::make($attributes['type']); } parent::__construct($attributes); } // * RELATIONSHIPS /** * Get the customer that owns the DepartaimantsCollection. */ public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } // * METHODS /** * Add a Departaiment to this collection. */ public function add(IDepartaimant $departaimant): void { $this->set($departaimant, DepartaimantStatus::has); } /** * Removes a Departaime from this collection. */ public function remove(IDepartaimant $departaimant): void { $this->reset($departaimant); } /** * Checks if this collection has a Departaimant. */ public function has(IDepartaimant $departaimant): bool { return $this->status($departaimant, DepartaimantStatus::has); } /** * Set the Departaimant to a specific status. */ public function set(IDepartaimant $departaimant, DepartaimantStatus $status): void { $depCol = static::depColInTable($this->type, $departaimant); $tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($this->type, $departaimant)); $this->reset($departaimant); // * OR $this->$tableName |= ($status->value << $depCol); } /** * Test the Departaimant status. */ public function status(IDepartaimant $departaimant, DepartaimantStatus $status): bool { $depCol = static::depColInTable($this->type, $departaimant); $tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($this->type, $departaimant)); /** * On créer le masque, puis on le décale vers la gauche de $depCol * On fait un & binaire pour récupérer la valeur * Puis on la redécale vers la droite * Enfin on la compare à l'énum DepartaimantStatus::has() */ $mask = bindec(str_replace('0', '1', decbin(self::$departementWidth))) << $depCol; return ($status->value << $depCol) === ($this->$tableName & $mask); } /** * Get all Customer which has their departaimant with a status. */ public static function getCustomersWithStatusAndDepartaimant( DepartaimantCollectionType $depColtype, IDepartaimant $departaimant, DepartaimantStatus $status ): \Illuminate\Database\Query\Builder { $depCol = self::depColInTable($depColtype, $departaimant); $tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($depColtype, $departaimant)); /** * On créer le masque, puis on le décale vers la gauche de $depCol * On fait un & binaire pour récupérer la valeur * Puis on la redécale vers la droite * Enfin on la compare à l'énum DepartaimantStatus::has()->value */ $mask = bindec(str_replace('0', '1', decbin(self::$departementWidth))) << $depCol; return Customer::query()->with('departaimantsCollections') ->whereHas('departaimantsCollections', function (Builder $query) use ($tableName, $mask, $status, $depCol) { $query->whereRaw(sprintf('((%d << %d) = (%s & %d))', $status->value, $depCol, $tableName, $mask)); })->getQuery(); } /** * Get all Customer which has their departaimant with a status. */ public static function getDepartaimantsCollectionWithStatus( array $departaimants, DepartaimantStatus $status, DepartaimantCollectionType $type ): \Illuminate\Database\Eloquent\Builder { $req = static::query()->where('type', $type->value); $whereReq = ''; $i = 0; foreach ($departaimants as $departaimant) { $depCol = self::depColInTable($type, $departaimant); $tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($type, $departaimant)); /** * On créer le masque, puis on le décale vers la gauche de $depCol * On fait un & binaire pour récupérer la valeur * Puis on la redécale vers la droite * Enfin on la compare à l'énum DepartaimantStatus::has()->value */ $mask = bindec(str_replace('0', '1', decbin(self::$departementWidth))) << $depCol; $whereReq .= sprintf('((%d << %d) = (%s & %d)) ', $status->value, $depCol, $tableName, $mask); if ($i++ !== count($departaimants) - 1) { $whereReq .= ' OR '; } } if (strlen($whereReq)) { $req->whereRaw("($whereReq)"); } return $req; } /** * Get the Departaimant status. */ public function getStatus(IDepartaimant $departaimant): DepartaimantStatus { $depCol = static::depColInTable($this->type, $departaimant); $tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($this->type, $departaimant)); /** * On créer le masque, puis on le décale vers la gauche de $depCol * On fait un & binaire pour récupérer la valeur * Puis on la redécale vers la droite * Enfin on la compare à l'énum DepartaimantStatus::has() */ $mask = bindec(str_replace('0', '1', decbin(self::$departementWidth))) << $depCol; return DepartaimantStatus::make(($this->$tableName & $mask) >> $depCol); } /** * Get a list of all the departaimants and their status. * * @uses \App\Enums\Departaimants\IDepartaimant * @uses \App\Enums\Departaimants\DepartaimantStatus * * @return array with * departaimant: \App\Enums\Departaimants\IDepartaimant, * status: \App\Enums\Departaimants\DepartaimantStatus */ public function getAllDepartaimants(): array { $deps = []; $className = self::getDepTypeClass($this->type); foreach (self::getDepKeys($this->type) as $depKey) { /** @var \App\Enums\Departaimants\IDepartaimant */ $dep = \forward_static_call([$className, 'make'], $depKey); $deps[$depKey] = (object) [ 'departaimant' => $dep, 'status' => $this->getStatus($dep), ]; } return $deps; } /** * Get a list of all the departaimants and their status. * * @uses \App\Enums\Departaimants\IDepartaimant * @uses \App\Enums\Departaimants\DepartaimantStatus * * @return array with * departaimant: \App\Enums\Departaimants\IDepartaimant, * status: \App\Enums\Departaimants\DepartaimantStatus */ public function getAllDepartaimantsWithStatus( DepartaimantCollectionType $depColtype, DepartaimantStatus $status ): array { $deps = []; $className = self::getDepTypeClass($depColtype); foreach (self::getDepKeys($depColtype) as $depKey) { /** @var \App\Enums\Departaimants\IDepartaimant */ $dep = \forward_static_call([$className, 'make'], $depKey); if ($status->equals($this->getStatus($dep))) { $deps[$depKey] = (object) [ 'departaimant' => $dep, 'status' => $this->getStatus($dep), ]; } } return $deps; } /** * Set the Departaimant column to 0. */ private function reset(IDepartaimant $departaimant): void { $depCol = self::depColInTable($this->type, $departaimant); $tableName = sprintf('%s%d', self::$columnTableNamePrefix, self::depField($this->type, $departaimant)); // AND max enum value. $this->$tableName &= ~(DepartaimantStatus::wants->value << $depCol); } /** * Donne la 'table' (colonne SQL) dans laquelle se trouve le département. */ public static function depField(DepartaimantCollectionType $depColtype, IDepartaimant $dep): int { $fieldCols = self::getTableColumnWith(); $totalFieldCols = self::$tableFields * $fieldCols; $depsPerField = floor($totalFieldCols / self::$tableFields); return (int) floor((array_search( '.' . $dep->value(), array_keys(self::getDeps($depColtype)) ) + $depsPerField) / $depsPerField); } /** * Donne la colonne (binaire) dans laquelle se trouve le département * dans sa 'table' (colonne SQL). */ public static function depColInTable(DepartaimantCollectionType $depColtype, IDepartaimant $dep): int { $tableColumnWith = self::getTableColumnWith(); $depsKeys = self::getDepKeys($depColtype); $i = 0; foreach ($depsKeys as $value) { if ($dep->value() == $value) { return $i * self::$departementWidth; } $i = $i >= ($tableColumnWith - 1) ? 0 : $i + 1; } return -1; } /** * Donne la valeur (masque binaire) d'un département. */ public function depVal(IDepartaimant $dep): int { return self::getDeps($this->type)['.' . $dep->value()]; } /** * Genère la liste des departements avec leur masques. */ private static function getDeps(DepartaimantCollectionType $depColtype): array { $tableColumnWith = self::getTableColumnWith(); $o = []; $i = 0; $depsKeys = self::getDepKeys($depColtype); foreach ($depsKeys as $a => $value) { // Appending '.' as a hack to prevent PHP from casting numeric indexes. $o['.' . $depsKeys[$a]] = 1 << $i * self::$departementWidth; $i = $i >= ($tableColumnWith - 1) ? 0 : $i + 1; } return $o; } /** * Get the with in bit length for a column * that can be used to store data. */ private static function getTableColumnWith(): float { // * bigInt = 8 * 8 octets. return floor(8 * 8 / self::$departementWidth) - self::$departementWidth; } /** * Get departaimant enum values. */ private static function getDepKeys(DepartaimantCollectionType $depColtype): array { $className = self::getDepTypeClass($depColtype); return \forward_static_call([$className, 'toValues']); } /** * Get a departaiment enum from a collection type. * * @throws \ErrorException If the type is not handled. */ private static function getDepTypeClass(DepartaimantCollectionType $depColtype): string { return match ($depColtype) { DepartaimantCollectionType::current => DepartaimantCurrent::class, DepartaimantCollectionType::vintage => DepartaimantVintage::class, // @phpstan-ignore-next-line default => throw new \ErrorException(sprintf( 'DepartaimantCollection::type %s is not handled in DepartaimantCollection Model', $depColtype )) }; } }