ldc-legaulois/app/Models/DepartaimantsCollection.php
2021-03-21 22:51:19 +01:00

423 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\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 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);
}
}
}