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/SBogers10/farmfun.komma.pro/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[] Defines how the sequence is structures */
    private $parts;

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

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

    /** @var string */
    protected $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): self
    {
        $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): self
    {
        $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): self
    {
        $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): self
    {
        //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)->whereNotNull($column)->where($column, '!=', '')->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.
            }
        }
    }
}