428 lines
14 KiB
PHP
428 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\Departaimant;
|
|
use App\Enums\DepartaimantCurrent;
|
|
use App\Enums\DepartaimantCollectionType;
|
|
use App\Enums\DepartaimantStatus;
|
|
use App\Enums\DepartaimantVintage;
|
|
use Kwaadpepper\Enum\Traits\CastsEnums;
|
|
use Exception;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
|
|
/**
|
|
* Departaimants Model
|
|
* Has functions to store departaimants on multiple statuses
|
|
*
|
|
* @uses App\Enums\Departaimant
|
|
* @uses App\Enums\DepartaimantStatus
|
|
* @uses App\Enums\DepartaimantCollectionType
|
|
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
|
|
*/
|
|
class DepartaimantsCollection extends Model
|
|
{
|
|
use HasFactory;
|
|
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 boolean
|
|
*/
|
|
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 boolean
|
|
*/
|
|
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 integer
|
|
*/
|
|
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 integer
|
|
*/
|
|
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 integer
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
}
|