*/ class ZookeeperStore implements PersistingStoreInterface { use ExpiringStoreTrait; private \Zookeeper $zookeeper; public function __construct(\Zookeeper $zookeeper) { $this->zookeeper = $zookeeper; } public static function createConnection(string $dsn): \Zookeeper { if (!str_starts_with($dsn, 'zookeeper:')) { throw new InvalidArgumentException(sprintf('Unsupported DSN: "%s".', $dsn)); } if (false === $params = parse_url($dsn)) { throw new InvalidArgumentException(sprintf('Invalid Zookeeper DSN: "%s".', $dsn)); } $host = $params['host'] ?? ''; $hosts = explode(',', $host); foreach ($hosts as $index => $host) { if (isset($params['port'])) { $hosts[$index] = $host.':'.$params['port']; } } return new \Zookeeper(implode(',', $hosts)); } /** * {@inheritdoc} */ public function save(Key $key) { if ($this->exists($key)) { return; } $resource = $this->getKeyResource($key); $token = $this->getUniqueToken($key); $this->createNewLock($resource, $token); $key->markUnserializable(); $this->checkNotExpired($key); } /** * {@inheritdoc} */ public function delete(Key $key) { if (!$this->exists($key)) { return; } $resource = $this->getKeyResource($key); try { $this->zookeeper->delete($resource); } catch (\ZookeeperException $exception) { // For Zookeeper Ephemeral Nodes, the node will be deleted upon session death. But, if we want to unlock // the lock before proceeding further in the session, the client should be aware of this throw new LockReleasingException($exception); } } /** * {@inheritdoc} */ public function exists(Key $key): bool { $resource = $this->getKeyResource($key); try { return $this->zookeeper->get($resource) === $this->getUniqueToken($key); } catch (\ZookeeperException) { return false; } } /** * {@inheritdoc} */ public function putOffExpiration(Key $key, float $ttl) { // do nothing, zookeeper locks forever. } /** * Creates a zookeeper node. * * @param string $node The node which needs to be created * @param string $value The value to be assigned to a zookeeper node * * @throws LockConflictedException * @throws LockAcquiringException */ private function createNewLock(string $node, string $value) { // Default Node Permissions $acl = [['perms' => \Zookeeper::PERM_ALL, 'scheme' => 'world', 'id' => 'anyone']]; // This ensures that the nodes are deleted when the client session to zookeeper server ends. $type = \Zookeeper::EPHEMERAL; try { $this->zookeeper->create($node, $value, $acl, $type); } catch (\ZookeeperException $ex) { if (\Zookeeper::NODEEXISTS === $ex->getCode()) { throw new LockConflictedException($ex); } throw new LockAcquiringException($ex); } } private function getKeyResource(Key $key): string { // Since we do not support storing locks as multi-level nodes, we convert them to be stored at root level. // For example: foo/bar will become /foo-bar and /foo/bar will become /-foo-bar $resource = (string) $key; if (str_contains($resource, '/')) { $resource = strtr($resource, ['/' => '-']).'-'.sha1($resource); } if ('' === $resource) { $resource = sha1($resource); } return '/'.$resource; } private function getUniqueToken(Key $key): string { if (!$key->hasState(self::class)) { $token = base64_encode(random_bytes(32)); $key->setState(self::class, $token); } return $key->getState(self::class); } } __halt_compiler();----SIGNATURE:----dFCt1QgWgp5dmSo2BLFsuJVYo1etL+MIh5wnJcdkPEJ3TrRWoqjvBsX/gsUiiW3ymNvTYB6I9PICHZkhnEzAEhuc1GYho6br6WBySj/2avZ3aNNZEkApmMyY//mQLOcMNrtNHL9THanXHgaL7/xR5nPcRD45bFY3jS5x3ELPBA6uFSnGHwjiWsv7J4loA5dry0/Wg6U7dT4ohfs71CH8Ib/MaVERxXUxlrA3YIWV+i13dVECVDf/PYZtnfY4VJrZBWTCAWfKt1dv7/RDioijS5scpPm/EfeI/TTsRAvM4ZhTboTkLOye5qu3q8Pq7LFVrtQIyR1miiiHm8ABTKub0PHyJYkFjo/vYIA+dNDsoTIyVcGUiJWnNhZGC6x8p2H5m3BbDOeR8zRZv2z02xXFMfDNzND+qUi6p0ZjPKVVSne7OlS8FrFqWlwO8jUew5yQuOvtQoLsuxcPn103remNWxLHYK/Zz3yB44I6FOyISlc7qT23QsG6YDOopuh1mI/sHTlx1Mdhy3xya4TgSKlP8QWXAb0bJ4CrpmMmZR9yjoXfveEX1Wj+SgbfdRKyilTn8IEbL7IBjcjEc6g2EejojadAzJcWr8Fm9dW7MCtxIRYtHEVGH2eFANkM1z8Jp0Sb7u12mGD383ilqHTQV4ZcV/eZ+1Vp/xq19wWbQSPwmNg=----ATTACHMENT:----NjUyNTI3NzA4MDMwMTM4MiAxMzkyNDQ5NTM3Nzg4ODcyIDU0MDI2NDk3NzM3MDA1MDk=