PluginProbe
Packeta / 2.1
Packeta v2.1
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / src / Packetery / Module / Options / FlagManager / FeatureFlagDownloader.php

FeatureFlagDownloader.php in Packeta 2.1, at src/Packetery/Module/Options/FlagManager/FeatureFlagDownloader.php

188 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class FeatureFlagDownloader
4 *
5 * @package Packetery
6 */
7
8 declare( strict_types=1 );
9
10 namespace Packetery\Module\Options\FlagManager;
11
12 use DateTimeImmutable;
13 use DateTimeZone;
14 use Exception;
15 use Packetery\Core\CoreHelper;
16 use Packetery\Module\Framework\WcAdapter;
17 use Packetery\Module\Framework\WpAdapter;
18 use Packetery\Module\Options\OptionsProvider;
19
20 /**
21 * Class FeatureFlagDownloader
22 *
23 * @package Packetery
24 */
25 class FeatureFlagDownloader {
26
27 private const VALID_FOR_SECONDS = 4 * HOUR_IN_SECONDS;
28 public const FLAGS_OPTION_ID = 'packeta_feature_flags';
29 public const DISABLED_DUE_ERRORS_OPTION_ID = 'packeta_feature_flags_disabled_due_errors';
30 private const ERROR_COUNTER_OPTION_ID = 'packeta_feature_flags_error_counter';
31
32 /**
33 * Endpoint url.
34 *
35 * @var string
36 */
37 private $endpointUrl;
38
39 /**
40 * Options provider.
41 *
42 * @var OptionsProvider
43 */
44 private $optionsProvider;
45
46 /**
47 * WP adapter;
48 *
49 * @var WpAdapter
50 */
51 private $wpAdapter;
52
53 /**
54 * WC adapter.
55 *
56 * @var WcAdapter
57 */
58 private $wcAdapter;
59
60 /**
61 * Feature flag store.
62 *
63 * @var FeatureFlagStorage
64 */
65 private $featureFlagStorage;
66
67 /**
68 * Downloader constructor.
69 *
70 * @param string $endpointUrl Endpoint url.
71 * @param OptionsProvider $optionsProvider Options provider.
72 * @param WpAdapter $wpAdapter WP adapter.
73 * @param WcAdapter $wcAdapter WC adapter.
74 * @param FeatureFlagStorage $featureFlagStorage Feature flag store.
75 */
76 public function __construct(
77 string $endpointUrl,
78 OptionsProvider $optionsProvider,
79 WpAdapter $wpAdapter,
80 WcAdapter $wcAdapter,
81 FeatureFlagStorage $featureFlagStorage
82 ) {
83 $this->endpointUrl = $endpointUrl;
84 $this->optionsProvider = $optionsProvider;
85 $this->wpAdapter = $wpAdapter;
86 $this->wcAdapter = $wcAdapter;
87 $this->featureFlagStorage = $featureFlagStorage;
88 }
89
90 /**
91 * Downloads flags.
92 *
93 * @return array<string, bool|string>
94 * @throws Exception From DateTimeImmutable.
95 */
96 private function fetchFlags(): array {
97 $response = $this->wpAdapter->remoteGet(
98 $this->wpAdapter->addQueryArg( [ 'api_key' => $this->optionsProvider->get_api_key() ], $this->endpointUrl ),
99 [ 'timeout' => 20 ]
100 );
101
102 if ( $this->wpAdapter->isWpError( $response ) ) {
103 $logger = $this->wcAdapter->createLogger();
104 $logger->warning( 'Packeta Feature flag API download error: ' . $response->get_error_message() );
105 $errorCount = $this->wpAdapter->getOption( self::ERROR_COUNTER_OPTION_ID, 0 );
106 $this->wpAdapter->updateOption( self::ERROR_COUNTER_OPTION_ID, $errorCount + 1 );
107 if ( $errorCount > 5 ) {
108 $this->wpAdapter->updateOption( self::DISABLED_DUE_ERRORS_OPTION_ID, true );
109 $logger->warning( 'Packeta Feature flag API download was disabled due to permanent connection errors.' );
110 }
111
112 return [];
113 }
114
115 $responseDecoded = json_decode( $this->wpAdapter->remoteRetrieveBody( $response ), true );
116 $lastDownload = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
117 $flags = [
118 FeatureFlagProvider::FLAG_SPLIT_ACTIVE => (bool) $responseDecoded['features']['split'],
119 FeatureFlagProvider::FLAG_LAST_DOWNLOAD => $lastDownload->format( CoreHelper::MYSQL_DATETIME_FORMAT ),
120 ];
121
122 $this->wpAdapter->updateOption( self::FLAGS_OPTION_ID, $flags );
123 $this->wpAdapter->updateOption( self::ERROR_COUNTER_OPTION_ID, 0 );
124
125 return $flags;
126 }
127
128 /**
129 * Gets or downloads flags.
130 *
131 * @return array<string, string>|null
132 * @throws Exception
133 */
134 public function getFlags(): ?array {
135 if ( $this->featureFlagStorage->getFlags() === null || count( $this->featureFlagStorage->getFlags() ) === 0 ) {
136 $flagsFromOptions = $this->wpAdapter->getOption( self::FLAGS_OPTION_ID );
137 if ( is_array( $flagsFromOptions ) ) {
138 $this->featureFlagStorage->setFlags( $flagsFromOptions );
139 }
140 }
141
142 if ( $this->wpAdapter->getOption( self::DISABLED_DUE_ERRORS_OPTION_ID ) === true ) {
143 return $this->featureFlagStorage->getFlags() ?? [];
144 }
145
146 $hasApiKey = ( $this->optionsProvider->get_api_key() !== null );
147 if ( $this->featureFlagStorage->getFlags() === null ) {
148 if ( ! $hasApiKey ) {
149 $this->featureFlagStorage->setFlags( [] );
150
151 return $this->featureFlagStorage->getFlags();
152 }
153
154 $this->featureFlagStorage->setFlags( $this->fetchFlags() );
155
156 return $this->featureFlagStorage->getFlags();
157 }
158
159 if ( $hasApiKey && ! $this->isLastDownloadValid() ) {
160 $this->featureFlagStorage->setFlags( $this->fetchFlags() );
161 }
162
163 return $this->featureFlagStorage->getFlags();
164 }
165
166 /**
167 * Checks if last download is still valid.
168 *
169 * @return bool
170 */
171 private function isLastDownloadValid(): bool {
172 if ( $this->featureFlagStorage->getFlag( FeatureFlagProvider::FLAG_LAST_DOWNLOAD ) === null ) {
173 // This should not happen, because the datetime is always set when fetching flags.
174 // But we want it not to be prone to errors.
175 return true;
176 }
177
178 $now = new DateTimeImmutable( 'now', new DateTimeZone( 'UTC' ) );
179 $lastUpdate = DateTimeImmutable::createFromFormat(
180 CoreHelper::MYSQL_DATETIME_FORMAT,
181 $this->featureFlagStorage->getFlag( FeatureFlagProvider::FLAG_LAST_DOWNLOAD ),
182 new DateTimeZone( 'UTC' )
183 );
184
185 return $now->getTimestamp() <= ( $lastUpdate->getTimestamp() + self::VALID_FOR_SECONDS );
186 }
187 }
188