*/ class DepartaimantsCollection extends Model { use CastsEnums; protected $enumCasts = [ 'type' => DepartaimantCollectionType::class ]; protected $table = 'departaimants'; private static $columnTableNamePrefix = 'col'; // Le prefix d'un table departaimant private static $tableFields = 4; // Le nombre de colonnes des departaimants private static $departementWidth = 2; // La largeur en bits d'un départaimant public $attributes = [ 'type' => 'current' // to be set in construct ]; public $fillable = [ 'collection' ]; public static function __callStatic($name, $arguments) { if ($name === 'defaultType') { return DepartaimantCollectionType::current(); } return parent::__callStatic($name, $arguments); } /** * @param array $attributes * @throws TypeError * @throws BadMethodCallException */ 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); } public function customer() { return $this->belongsTo(Customers::class); } /** * Add a Departaiment to this collection * * @param Departaimant $departaimant * @return void */ public function add(Departaimant $departaimant): void { $this->set($departaimant, DepartaimantStatus::has()); } /** * Removes a Departaime from this collection * * @param Departaimant $departaimant * @return void */ public function remove(Departaimant $departaimant): void { $this->reset($departaimant); } /** * Checks if this collection has a Departaimant * * @param Departaimant $departaimant * @return bool */ public function has(Departaimant $departaimant): bool { return $this->status($departaimant, DepartaimantStatus::has()); } /** * Set the Departaimant to a specific status * * @param Departaimant $departaimant * @param DepartaimantStatus $status * @return void */ public function set(Departaimant $departaimant, DepartaimantStatus $status): void { $depCol = static::depColInTable($this->type, $departaimant); $tableName = sprintf('%s%d', static::$columnTableNamePrefix, static::depField($this->type, $departaimant)); $this->reset($departaimant); $this->$tableName |= ($status->value << $depCol); // OR } /** * Test the Departaimant status * * @param Departaimant $departaimant * @param DepartaimantStatus $status * @return bool */ public function status(Departaimant $departaimant, DepartaimantStatus $status): bool { $depCol = static::depColInTable($this->type, $departaimant); $tableName = sprintf('%s%d', static::$columnTableNamePrefix, static::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(static::$departementWidth))) << $depCol; return ($status->value << $depCol) === ($this->$tableName & $mask); } /** * Get all Customer which has their departaimant with a status * * @param DepartaimantCollectionType $depColtype * @param Departaimant $departaimant * @param DepartaimantStatus $status * @return Illuminate\Database\Eloquent\Builder */ public static function getCustomersWithStatusAndDepartaimant( DepartaimantCollectionType $depColtype, Departaimant $departaimant, DepartaimantStatus $status ): 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 Customers::with('departaimantsCollections') ->whereHas('departaimantsCollections', function ($query) use ($tableName, $mask, $status, $depCol) { $query->whereRaw(sprintf('((%d << %d) = (%s & %d))', $status->value, $depCol, $tableName, $mask)); }); } /** * Get all Customer which has their departaimant with a status * * @param array $departaimants * @param DepartaimantStatus $status * @param DepartaimantCollectionType $type * @return Builder */ public static function getDepartaimantsCollectionWithStatus( array $departaimants, DepartaimantStatus $status, DepartaimantCollectionType $type ): Builder { $req = static::where('type', $type->value); $whereReq = ''; $i = 0; foreach ($departaimants as $k => $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 * * @param Departaimant $departaimant * @return DepartaimantStatus */ public function getStatus(Departaimant $departaimant): DepartaimantStatus { $depCol = static::depColInTable($this->type, $departaimant); $tableName = sprintf('%s%d', static::$columnTableNamePrefix, static::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(static::$departementWidth))) << $depCol; return DepartaimantStatus::make(($this->$tableName & $mask) >> $depCol); } /** * Get a list of all the departaimants and their status * * @uses Departaimant * @uses DepartaimantStatus * @return array[object] with departaimant: \App\Enums\Departaimant, status:\App\Enums\DepartaimantStatus */ public function getAllDepartaimants(): array { $deps = []; /** @var Departaimant */ $departaimant = static::getDepType($this->type); foreach (static::getDepKeys($this->type) as $depKey) { /** @var Departaimant */ $dep = \forward_static_call([\get_class($departaimant), 'make'], $depKey); $deps[$depKey] = (object)[ 'departaimant' => $dep, 'status' => $this->getStatus($dep) ]; } return $deps; } /** * Get a list of all the departaimants and their status * * @param DepartaimantCollectionType $depColtype * @param DepartaimantStatus $status * @uses Departaimant * @uses DepartaimantStatus * @return array[object] with departaimant: \App\Enums\Departaimant, status:\App\Enums\DepartaimantStatus */ public function getAllDepartaimantsWithStatus( DepartaimantCollectionType $depColtype, DepartaimantStatus $status ): array { $deps = []; /** @var Departaimant */ $departaimant = static::getDepType($depColtype); foreach (static::getDepKeys($depColtype) as $depKey) { /** @var Departaimant */ $dep = \forward_static_call([\get_class($departaimant), '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 * * @param Departaimant $departaimant * @return void */ private function reset(Departaimant $departaimant): void { $depCol = static::depColInTable($this->type, $departaimant); $tableName = sprintf('%s%d', static::$columnTableNamePrefix, static::depField($this->type, $departaimant)); $this->$tableName &= ~(DepartaimantStatus::wants()->value << $depCol); // AND max enum value } /** * Donne la 'table' (colonne SQL) dans laquelle se trouve * le département * * @param DepartaimantCollectionType $depColtype * @param Departaimant $dep * @return int */ public static function depField(DepartaimantCollectionType $depColtype, Departaimant $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) * * @param DepartaimantCollectionType $depColtype * @param Departaimant $dep * @return int */ public static function depColInTable(DepartaimantCollectionType $depColtype, Departaimant $dep): int { $tableColumnWith = self::getTableColumnWith(); $depsKeys = self::getDepKeys($depColtype); $size = count($depsKeys); $i = 0; for ($a = 0; $a < $size; $a++) { if ($dep->value == $depsKeys[$a]) { return $i * self::$departementWidth; } $i = $i >= ($tableColumnWith - 1) ? 0 : $i + 1; } return -1; } /** * Donne la valeur (masque binaire) d'un département * * @param Departaimant $dep * @return int */ public function depVal(Departaimant $dep): int { return static::getDeps($this->type)['.' . $dep->value]; } /** * Genère la liste des departements avec leur masques * * @param DepartaimantCollectionType $depColtype * @return array */ private static function getDeps(DepartaimantCollectionType $depColtype): array { $tableColumnWith = self::getTableColumnWith(); $o = []; $i = 0; $depsKeys = self::getDepKeys($depColtype); $size = count($depsKeys); for ($a = 0; $a < $size; $a++) { // 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 * * @return void */ private static function getTableColumnWith() { // bigInt = 8 * 8 octets return floor(8 * 8 / self::$departementWidth) - self::$departementWidth; } /** * Get departaimant enum values * * @param DepartaimantCollectionType $depColtype * @return array */ private static function getDepKeys(DepartaimantCollectionType $depColtype): array { /** @var Departaimant */ $type = self::getDepType($depColtype); return \forward_static_call([\get_class($type), 'toValues']); } /** * Get a departaiment enum from a collection type * * @param DepartaimantCollectionType $depColtype * @return Departaimant */ private static function getDepType(DepartaimantCollectionType $depColtype): Departaimant { if (is_null($depColtype)) { throw new Exception( 'DepartaimantCollection::type class member must be set to a DepartaimantCollectionType' ); } switch ($depColtype) { case DepartaimantCollectionType::current(): return new DepartaimantCurrent(); case DepartaimantCollectionType::vintage(): return new DepartaimantVintage(); default: throw new Exception(sprintf( 'DepartaimantCollection::type %s is not handled in DepartaimantCollection Model' ), $depColtype); } } }