ldc-legaulois/app/Models/DepartaimantsCollection.php
2025-06-11 09:59:07 +02:00

482 lines
17 KiB
PHP

<?php
namespace App\Models;
use App\Enums\Departaimants\DepartaimantCollectionType;
use App\Enums\Departaimants\DepartaimantCurrent;
use App\Enums\Departaimants\DepartaimantStatus;
use App\Enums\Departaimants\DepartaimantVintage;
use App\Enums\Departaimants\IDepartaimant;
use Illuminate\Contracts\Database\Query\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* Departaimants Model
* Has functions to store departaimants on multiple statuses.
*
* @property \App\Enums\Departaimants\DepartaimantCollectionType $type
* @property integer $customer_id
*
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*
* @property-read \App\Models\Customer $customer Customer BelongsTo relationship.
*
* @uses App\Enums\Departaimant
* @uses App\Enums\DepartaimantStatus
* @uses App\Enums\DepartaimantCollectionType
*/
class DepartaimantsCollection extends Model
{
use HasFactory;
/**
* The attributes that should be cast.
*
* @var array<string,string>
*/
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<integer,string>
*/
public $fillable = [
'type'
];
/**
* Columns prefix.
*
* @var string
*/
private static $columnTableNamePrefix = 'col';
/**
* Le prefix d'un table departaimant.
*
* @var integer
*/
private static $tableFields = 4;
/**
* Le nombre de colonnes des departaimants.
*
* * La largeur en bits d'un départaimant
* * to be set in construct
*
* @var integer
*/
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.
*
* @param array $attributes
*/
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.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function customer(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Customer::class);
}
// * METHODS
/**
* Add a Departaiment to this collection.
*
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
* @return void
*/
public function add(IDepartaimant $departaimant): void
{
$this->set($departaimant, DepartaimantStatus::has);
}
/**
* Removes a Departaime from this collection.
*
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
* @return void
*/
public function remove(IDepartaimant $departaimant): void
{
$this->reset($departaimant);
}
/**
* Checks if this collection has a Departaimant.
*
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
* @return boolean
*/
public function has(IDepartaimant $departaimant): bool
{
return $this->status($departaimant, DepartaimantStatus::has);
}
/**
* Set the Departaimant to a specific status.
*
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
* @param \App\Enums\Departaimants\DepartaimantStatus $status
* @return void
*/
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.
*
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
* @param \App\Enums\Departaimants\DepartaimantStatus $status
* @return boolean
*/
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.
*
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
* @param \App\Enums\Departaimants\DepartaimantStatus $status
* @return \Illuminate\Database\Query\Builder
*/
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.
*
* @param array $departaimants
* @param \App\Enums\Departaimants\DepartaimantStatus $status
* @param \App\Enums\Departaimants\DepartaimantCollectionType $type
* @return \Illuminate\Database\Eloquent\Builder
*/
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.
*
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
* @return \App\Enums\Departaimants\DepartaimantStatus
*/
public function getStatus(IDepartaimant $departaimant): \App\Enums\Departaimants\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<object> 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.
*
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
* @param \App\Enums\Departaimants\DepartaimantStatus $status
* @uses \App\Enums\Departaimants\IDepartaimant
* @uses \App\Enums\Departaimants\DepartaimantStatus
* @return array<object> 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.
*
* @param \App\Enums\Departaimants\IDepartaimant $departaimant
* @return void
*/
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.
*
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
* @param \App\Enums\Departaimants\IDepartaimant $dep
* @return integer
*/
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).
*
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
* @param \App\Enums\Departaimants\IDepartaimant $dep
* @return integer
*/
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.
*
* @param \App\Enums\Departaimants\IDepartaimant $dep
* @return integer
*/
public function depVal(IDepartaimant $dep): int
{
return self::getDeps($this->type)['.' . $dep->value()];
}
/**
* Genère la liste des departements avec leur masques.
*
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
* @return array
*/
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.
*
* @return float
*/
private static function getTableColumnWith(): float
{
// * bigInt = 8 * 8 octets.
return floor(8 * 8 / self::$departementWidth) - self::$departementWidth;
}
/**
* Get departaimant enum values.
*
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
* @return array
*/
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.
*
* @param \App\Enums\Departaimants\DepartaimantCollectionType $depColtype
* @return string
* @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
))
};
}
}