1<?php 2/* 3 * Copyright 2022 Google Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18namespace Google\Auth\Cache; 19 20use Psr\Cache\CacheItemInterface; 21 22/** 23 * A cache item. 24 */ 25final class TypedItem implements CacheItemInterface 26{ 27 /** 28 * @var mixed 29 */ 30 private mixed $value; 31 32 /** 33 * @var \DateTimeInterface|null 34 */ 35 private ?\DateTimeInterface $expiration; 36 37 /** 38 * @var bool 39 */ 40 private bool $isHit = false; 41 42 /** 43 * @param string $key 44 */ 45 public function __construct( 46 private string $key 47 ) { 48 $this->key = $key; 49 $this->expiration = null; 50 } 51 52 /** 53 * {@inheritdoc} 54 */ 55 public function getKey(): string 56 { 57 return $this->key; 58 } 59 60 /** 61 * {@inheritdoc} 62 */ 63 public function get(): mixed 64 { 65 return $this->isHit() ? $this->value : null; 66 } 67 68 /** 69 * {@inheritdoc} 70 */ 71 public function isHit(): bool 72 { 73 if (!$this->isHit) { 74 return false; 75 } 76 77 if ($this->expiration === null) { 78 return true; 79 } 80 81 return $this->currentTime()->getTimestamp() < $this->expiration->getTimestamp(); 82 } 83 84 /** 85 * {@inheritdoc} 86 */ 87 public function set(mixed $value): static 88 { 89 $this->isHit = true; 90 $this->value = $value; 91 92 return $this; 93 } 94 95 /** 96 * {@inheritdoc} 97 */ 98 public function expiresAt($expiration): static 99 { 100 if ($this->isValidExpiration($expiration)) { 101 $this->expiration = $expiration; 102 103 return $this; 104 } 105 106 $error = sprintf( 107 'Argument 1 passed to %s::expiresAt() must implement interface DateTimeInterface, %s given', 108 get_class($this), 109 gettype($expiration) 110 ); 111 112 throw new \TypeError($error); 113 } 114 115 /** 116 * {@inheritdoc} 117 */ 118 public function expiresAfter($time): static 119 { 120 if (is_int($time)) { 121 $this->expiration = $this->currentTime()->add(new \DateInterval("PT{$time}S")); 122 } elseif ($time instanceof \DateInterval) { 123 $this->expiration = $this->currentTime()->add($time); 124 } elseif ($time === null) { 125 $this->expiration = $time; 126 } else { 127 $message = 'Argument 1 passed to %s::expiresAfter() must be an ' . 128 'instance of DateInterval or of the type integer, %s given'; 129 $error = sprintf($message, get_class($this), gettype($time)); 130 131 throw new \TypeError($error); 132 } 133 134 return $this; 135 } 136 137 /** 138 * Determines if an expiration is valid based on the rules defined by PSR6. 139 * 140 * @param mixed $expiration 141 * @return bool 142 */ 143 private function isValidExpiration($expiration) 144 { 145 if ($expiration === null) { 146 return true; 147 } 148 149 // We test for two types here due to the fact the DateTimeInterface 150 // was not introduced until PHP 5.5. Checking for the DateTime type as 151 // well allows us to support 5.4. 152 if ($expiration instanceof \DateTimeInterface) { 153 return true; 154 } 155 156 return false; 157 } 158 159 /** 160 * @return \DateTime 161 */ 162 protected function currentTime() 163 { 164 return new \DateTime('now', new \DateTimeZone('UTC')); 165 } 166} 167