HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/stafa/werkenbijstafa.nl/app/Komma/Kms/Core/Sequence/Sequence.php
<?php
namespace App\Komma\Kms\Core\Sequence;

use App\Komma\Kms\Core\Sequence\Parts\AbstractPart;
use Illuminate\Support\Facades\DB;

/**
 * Class Sequence
 *
 * Builds sequences.
 */
class Sequence {
    /** @var AbstractPart[] $parts Defines how the sequence is structures*/
    private $parts;

    /** @var bool */
    private $lastPartMayOverflow = false;

    /** @var string */
    private $table;

    /** @var string */
    private $column;

    public function __construct()
    {
        $this->parts = [];
    }

    /**
     * Add a part that describes a part of the sequence's structure to the beginning of the describing structure.
     *
     * @param $part
     * @return Sequence
     */
    public function startsWith($part): Sequence
    {
        $this->validatePart($part);
        array_unshift($this->parts, $part);

        return $this;
    }

    /**
     * Add a part that describes a part of the sequence's structure to the end of the describing structure
     *
     * @param $part
     * @return Sequence
     */
    public function followedBy($part): Sequence
    {
        $this->validatePart($part);
        $this->parts[] = $part;

        return $this;
    }

    /**
     * Seeds / primes the sequence with a code / number to start from.
     *
     * @param string $value
     * @return Sequence
     */
    public function startingAt(string $value): Sequence
    {
        $originalValue = $value;
        //Fill the parts of the sequence with their values
        $copiedParts = $this->parts;
        while($currentPart = array_shift($copiedParts))
        {
            if(is_a($currentPart, AbstractPart::class)) {
                //Give the part the whole value, it will shorten it internally when needed, and set its starting value appropriately
                $mayOverflow = ($this->lastPartMayOverflow && count($copiedParts) == 0);
                $currentPart->startingAt($value, $mayOverflow);
                $length = mb_strlen($currentPart->getValue());
            } else {
                //If it is not an Abstract part, it is a string, and we just need to strip that string from the value.
                $length = mb_strlen($currentPart);
            }

            //Remove the currentPart value of the value. The remaining data will be fed into the next part.
            $value = substr($value, $length);
        }

        if($value !== '') {
            throw new \InvalidArgumentException('Sequence: The value was invalid. The sequence must be '.$this->describe().'Was: '.$originalValue);
        }

        return $this;
    }

    /**
     * Generate a new code based on the sequence
     * You can specify which part of the sequence to increment.
     * Do this by passing the zero based index of the part in the sequence, or by passing a name of the part.
     * If you don't specify a which part to increment, the last part will be incremented.
     *
     * @param null|string|int $partIdentifier
     * @return Sequence
     */
    public function next($partIdentifier = null): Sequence
    {
        //If no part identifier is given. We set the identifier to the last part
        $lastPartIdentifierNumber = count($this->getParts()) - 1;
        if($partIdentifier == null) $partIdentifier = $lastPartIdentifierNumber;

        //Get the part we need to "next".
        if(is_string($partIdentifier)) {
            $part = $this->getPartByName($partIdentifier);
        } elseif(is_int($partIdentifier)) {
            if($partIdentifier > $lastPartIdentifierNumber) throw new \OutOfBoundsException('Sequence: You wanted to increment part number "'.$partIdentifier.'". But the last partIdentifier is "'.$lastPartIdentifierNumber.'"');
            $part = $this->getParts()[$partIdentifier];
        } else {
            throw new \InvalidArgumentException('Sequence: The partNumber to increment must either be null, an zero based integer referencing to a part, or a name of a part');
        }

        if(is_string($part)) throw new \InvalidArgumentException('Sequence: Could not increment the string "'.$partIdentifier.'" obviously.');
        $part->next();
        $this->throwExceptionWhenOverflownIllegally();
        if(!$this->isUnique($this)) throw new \RuntimeException('Sequence: Value "'.$this.'" is not unique in database table "'.$this->table.'", column "'.$this->column.'"');
        return $this;
    }

    /**
     * Throws an exception when one of the parts in the sequence did overflow.
     * The last part may overflow only when lastPartMayOverflow was called.
     *
     * @see lastPartMayOverflow
     */
    public function throwExceptionWhenOverflownIllegally()
    {
        $itemsCount = count($this->parts);
        foreach($this->parts as $index => $part)
        {
            if(is_string($part)) continue;
            if($part->overflownBecause() === '' || ($this->lastPartMayOverflow && $index = $itemsCount - 1)) continue;
            $partName = $part->getName() == '' ?: ' ('.$part->getName().')';
            throw new \OutOfBoundsException('Sequence: Part '.$index.$partName.' did overflow because '.$part->overflownBecause());
        }
    }

    /**
     * Converts the sequence to a string when it is used in a string, or is casted to a string.
     *
     * @return string
     */
    public function __toString()
    {
        $currentValue = '';
        foreach ($this->parts as $currentPart) {
            $currentValue .= (is_a($currentPart, AbstractPart::class)) ? $currentPart->getValue() : $currentPart;
        }

        return $currentValue;
    }

    /**
     * Validates a part.
     *
     * @param $part
     * @return bool
     */
    private function validatePart($part) {
        if(!is_string($part) && !is_a($part, AbstractPart::class)) {
            throw new \InvalidArgumentException('The given "part" is neither a string or an subclass of '.AbstractPart::class);
        }
        return true;
    }

    /**
     * Returns a human readable description of how the sequence structure looks like
     *
     * @return string
     */
    public function describe():string {
        $description = '';

        foreach ($this->parts as $index => $currentPart) {
            $description .= $index == 0 ? 'starting with ' : 'Followed by ';

            if(is_a($currentPart, AbstractPart::class)) {
                $description .= $currentPart->describe().' ';
            } else {
                $description .= 'the string "'.$currentPart.'". ';
            }
        }

        return $description;
    }

    /**
     * Searches through the part of the sequence to find a part by its name.
     * If more then one part share the same name, The first one is returned.
     *
     * @param string $name
     * @return string|AbstractPart
     */
    public function getPartByName(string $name)
    {
        foreach($this->parts as $part)
        {
            if(is_a($part, AbstractPart::class)) {
                if(strtolower($part->getName()) == strtolower($name)) return $part; //Part found a a child of AbstractPart. Return that.
            } elseif(is_string($part)) {
                if(strtolower($name) == strtolower($part)) return $part; //Part found as string. Simply return the string.
            }
        }
        throw new \InvalidArgumentException('Sequence: i don\'t have have a part called "'.$name.'"');
    }

    /**
     * @return AbstractPart[]
     */
    public function getParts(): array
    {
        return $this->parts;
    }

    /**
     * Allows the last part in the sequence to overflow.
     */
    public function lastPartMayOverflow()
    {
        $this->lastPartMayOverflow = true;
    }

    /**
     * @param string $table
     * @param string $column
     * @return $this
     */
    public function uniqueForTable(string $table, string $column) {
        $this->table = $table;
        $this->column = $column;
        $latestRecords = DB::table($table)->orderBy('id', 'DESC')->limit(1)->get([$column]);
        if($latestRecords->count() == 1) {
            $latestSequenceValueFromTable = $latestRecords[0]->$column;
            $this->startingAt($latestSequenceValueFromTable);
        }

        return $this;
    }

    /**
     * Check the table and column if the given value is unique.
     * If you did not specify a table and column, this method always returns true.
     * This function is also used internally by the next method
     *
     * @see next()
     * @param string $value
     * @return bool
     */
    public function isUnique(string $value) {
        if(!$this->table && !$this->column) return true;
        $latestRecords = DB::table($this->table)->where($this->column, '=', $value)->get([$this->column]);
        return count($latestRecords) == 0;
    }

    /**
     * Clones itself
     */
    public function __clone()
    {
        foreach ($this as $key => $val) {
            if (is_object($val) || (is_array($val))) {
                $this->{$key} = unserialize(serialize($val)); //Unserializing a serialized value is the trick for a deep copy.
            }
        }
    }
}