phonetworks/pho-lib-graph

View on GitHub
src/Pho/Lib/Graph/ID.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php declare(strict_types=1);

/*
 * This file is part of the Pho package.
 *
 * (c) Emre Sokullu <emre@phonetworks.org>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Pho\Lib\Graph;

use Pho\Lib\DHT\Utils;
use Pho\Lib\DHT\IDInterface;

/**
 * Immutable, cryptographically secure identifier
 * 
 * Pho IDs are immutable and come in the format of cryptographically secure,
 * similarly to 
 * {@link https://en.wikipedia.org/wiki/Universally_unique_identifier UUIDv4},
 * though not the same.
 * 
 * Pho IDs are used to define all graph entities, e.g nodes and edges.
 * It is 16 bytes (128 bits) long similar to UUID, but the first byte is
 * reserved to determine entity type, while the UUID variants are omitted.
 * Hence, Pho ID provides 15 bytes of randomness.
 * 
 * The Graph ID defaults to nil (00000000000000000000000000000000), or 32 chars
 * of 0. It may may be called with ```ID::root()```
 * 
 * Even at scale of billions of nodes and edges, the chances of collision 
 * is identical to zero.
 * 
 * You can generate a new ID with ```$id_object = ID::generate($entity)```, 
 * where $entity is any Pho entity, and fetch its  string representation with 
 * PHP type-casting; ```(string) $id_object```.
 * 
 * @author Emre Sokullu <emre@phonetworks.org>
 */
class ID implements IDInterface
{
    
    const BitLength = 128; // UUID
    const ByteLength = (self::BitLength / 8);

    /**
     * Pho ID in string.
     * 
     * In Hex format
     *
     * @var string
     */
    protected $value;

    /**
     * @internal
     * Constructor. 
     * 
     * Can't be accessed from outside. Use ```ID::generate()```
     * to create a new random ID.
     * 
     * @see ID::generate() To form a new ID object.
     *
     * @param string $id
     */
    private function __construct(string $id) 
    {
        $this->value = $id;
    }

    /**
     * {@inheritdoc}
     */
    public function distance(/*mixed*/ $another_id): string
    {
        if($another_id instanceof ID)
            $another_id = $another_id->toString();
        if(!is_string($another_id)) {
            throw new \InvalidArgumentException("This method only accepts string or \\Pho\\Lib\\Graph inputs. The parameter was a ".gettype($another_id));
        }
        return (string) Utils::xor_bucket($this->value, $another_id);

    }

    /**
     * {@inheritDoc}
     */
    public function bin(): string
    {
        return Utils::hex_to_bin($this->value, self::BitLength);
    }

    /**
     * Generates a cryptographically secure random ID for internal use.
     *
     * Pho ID does not conform to UUID standards. It is similar to UUID v4, 
     * however it does not use the same variants at same locations. Instead,
     * the first byte is reserved for entity type, and the remaining 15 is 
     * used for randomness.
     * 
     * @link https://en.wikipedia.org/wiki/Universally_unique_identifier UUIDv4 format
     *
     * @return ID  Random ID in object format.
     */
    public static function generate(EntityInterface $entity): ID
    {
        $headers = static::header($entity);
        return new ID(
            sprintf("%x%x%s", 
                $headers[0], 
                $headers[1],
                bin2hex(
                    random_bytes(15)
                )
            )
        );
    }

     /**
      * Generates an ID for the header
      *
      * This method enables flexibility when it comes to naming an 
      * entity header by extending ID class.
      *
      * @param EntityInterface $entity
      * @return array An array of two ints (actually hexadecimals) 
      */
    protected static function header(EntityInterface $entity): array
    {
        return [rand(0,15), rand(0,15)];
    }
    
    /**
     * Loads a Pho ID with the given string
     * 
     * Checks the validity of the string and throws an exception if it is not valid.
     *
     * @param string $id Must consist of 32 hexadecimal characters.
     * 
     * @return ID The ID in object format
     * 
     * @throws Exceptions\MalformedIDException thrown when the given ID is not a valid UUIDv4
     */
    public static function fromString(string $id): ID
    {
        $uuid_format = '/^[0-9A-F]{32}$/i';
        if(!preg_match($uuid_format, $id)) {
            throw new Exceptions\MalformedIDException($id);
        }
        return new ID($id);
    }

    /**
     * Retrieves the root ID
     * 
     * Root ID is the ID of the Graph. It doesn't conform with regular
     * ID requirements (namely UUID) and it is just a period (.)
     *
     * @return ID
     */
    public static function root(): ID
    {
        return new ID("00000000000000000000000000000000");
    }

    /**
     * Verifies identicality
     *
     * @param mixed $id ID as ID object or string
     * @return bool
     */
    public function equals(/*ID|string*/ $id) 
    {
        return ($this->value == (string) $id);
    }

    /**
     * {@internal}
     * 
     * Stringifies the object.
     * 
     * Returns a string representation of the object for portability.
     * Use with PHP 
     * {@link http://php.net/manual/en/language.types.type-juggling.php#language.types.typecasting type-casting}
     * as follows; ```(string) $ID_object```
     *
     * @return string
     */
    public function __toString(): string
    {
        return $this->value;
    }

    /**
     * Stringifies the object.
     * 
     * Returns a string representation of the object for portability.
     * Use with PHP 
     * {@link http://php.net/manual/en/language.types.type-juggling.php#language.types.typecasting type-casting}
     * as follows; ```(string) $ID_object```
     *
     * @return string
     */
    public function toString(): string
    {
        return $this->value;
    }

}