Elgg  Version 5.1
Loop.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Upgrade;
4 
7 use Elgg\Logger;
10 
15 class Loop {
16 
17  use Loggable;
18 
22  protected $upgrade;
23 
27  protected $result;
28 
32  protected $batch;
33 
37  protected $max_duration;
38 
42  protected $count;
43 
47  protected $processed;
48 
52  protected $offset;
53 
57  protected $progress;
58 
69  public function __construct(
73  Logger $logger
74  ) {
75  $this->upgrade = $upgrade;
76 
77  // Get the class taking care of the actual upgrading
78  $this->batch = $upgrade->getBatch();
79  if (!$this->batch) {
80  throw new RuntimeException(elgg_echo('admin:upgrades:error:invalid_batch', [
81  $upgrade->getDisplayName(),
82  $upgrade->guid
83  ]));
84  }
85 
86  $this->result = $result;
87  $this->progress = $progress;
88  $this->logger = $logger;
89 
90  $this->count = $this->batch->countItems();
91  $this->processed = (int) $upgrade->processed;
92  $this->offset = (int) $upgrade->offset;
93  }
94 
102  public function loop($max_duration = null): void {
103 
104  $started = microtime(true);
105 
106  $this->upgrade->setStartTime();
107  $progress = $this->progress->start($this->upgrade->getDisplayName(), $this->count);
108 
109  while ($this->canContinue($started, $max_duration)) {
110  $this->runBatch($progress);
111  }
112 
113  $this->progress->finish($progress);
114 
115  $this->upgrade->processed = $this->processed;
116  $this->upgrade->offset = $this->offset;
117 
118  if (!$this->isCompleted()) {
119  return;
120  }
121 
122  // Upgrade is finished
123  if ($this->result->getFailureCount()) {
124  // The upgrade was finished with errors. Reset offset
125  // and errors so the upgrade can start from a scratch
126  // if attempted to run again.
127  $this->upgrade->processed = 0;
128  $this->upgrade->offset = 0;
129  } else {
130  // Everything has been processed without errors
131  // so the upgrade can be marked as completed.
132  $this->upgrade->setCompleted();
133  $this->result->markComplete();
134  }
135 
136  $this->report();
137  }
138 
146  protected function runBatch(ProgressBar $progress): void {
147  try {
148  $this->batch->run($this->result, $this->offset);
149  } catch (\Exception $e) {
150  $this->getLogger()->error($e);
151 
152  $this->result->addError($e->getMessage());
153  $this->result->addFailures(1);
154  }
155 
156  $failure_count = $this->result->getFailureCount();
157  $success_count = $this->result->getSuccessCount();
158 
159  $total = $this->upgrade->processed + $failure_count + $success_count;
160 
161  $progress->advance($total - $this->processed);
162 
163  if ($this->batch->needsIncrementOffset()) {
164  // Offset needs to incremented by the total amount of processed
165  // items so the upgrade we won't get stuck upgrading the same
166  // items over and over.
167  $this->offset = $total;
168  } else {
169  // Offset doesn't need to be incremented, so we mark only
170  // the items that caused a failure.
171  $this->offset = $this->upgrade->offset + $failure_count;
172  }
173 
174  $this->processed = $total;
175  }
176 
182  protected function report(): void {
183  $upgrade_name = $this->upgrade->getDisplayName();
184 
185  if ($this->upgrade->isCompleted()) {
186  $ts = $this->upgrade->getCompletedTime();
187  $dt = new \DateTime();
188  $dt->setTimestamp((int) $ts);
189  $format = elgg_get_config('date_format') ?: DATE_ATOM;
190 
191  if ($this->result->getFailureCount()) {
192  elgg_register_error_message(elgg_echo('admin:upgrades:completed:errors', [
193  $upgrade_name,
194  $dt->format($format),
195  $this->result->getFailureCount(),
196  ]));
197  } else {
198  elgg_register_success_message(elgg_echo('admin:upgrades:completed', [
199  $upgrade_name,
200  $dt->format($format),
201  ]));
202  }
203  } else {
204  elgg_register_error_message(elgg_echo('admin:upgrades:failed', [$upgrade_name]));
205  }
206 
207  foreach ($this->result->getErrors() as $error) {
208  $this->getLogger()->error($error);
209  }
210  }
211 
220  protected function canContinue($started, $max_duration = null): bool {
221  if (!isset($max_duration)) {
222  $max_duration = elgg_get_config('batch_run_time_in_secs');
223  }
224 
225  if ($max_duration && (microtime(true) - $started) >= $max_duration) {
226  return false;
227  }
228 
229  return !$this->isCompleted();
230  }
231 
237  protected function isCompleted(): bool {
238  if ($this->batch->shouldBeSkipped()) {
239  return true;
240  }
241 
242  if ($this->result->wasMarkedComplete()) {
243  return true;
244  }
245 
246  if ($this->count === Batch::UNKNOWN_COUNT) {
247  // the batch reports an unknown count and should mark the Result as complete when it's done
248  return false;
249  }
250 
251  if (!$this->batch->needsIncrementOffset()) {
252  // the batch has some way of marking progress (like a delete) and the count items should reflect this
253  return ($this->batch->countItems() - $this->result->getFailureCount()) <= 0;
254  }
255 
256  return $this->processed >= $this->count;
257  }
258 }
$format
Definition: date.php:37
$started
Definition: completed.php:15
Exception thrown if an error which can only be found on runtime occurs.
elgg_get_config(string $name, $default=null)
Get an Elgg configuration value.
__construct(\ElggUpgrade $upgrade, Result $result, Progress $progress, Logger $logger)
Constructor.
Definition: Loop.php:69
canContinue($started, $max_duration=null)
Check if the loop cand and should continue.
Definition: Loop.php:220
runBatch(ProgressBar $progress)
Run batch.
Definition: Loop.php:146
elgg_register_success_message(string|array $options)
Registers a success system message.
Definition: elgglib.php:43
c Accompany it with the information you received as to the offer to distribute corresponding source complete source code means all the source code for all modules it plus any associated interface definition plus the scripts used to control compilation and installation of the executable as a special the source code distributed need not include anything that is normally and so on of the operating system on which the executable unless that component itself accompanies the executable If distribution of executable or object code is made by offering access to copy from a designated then offering equivalent access to copy the source code from the same place counts as distribution of the source even though third parties are not compelled to copy the source along with the object code You may not or distribute the Program except as expressly provided under this License Any attempt otherwise to sublicense or distribute the Program is void
Definition: LICENSE.txt:215
elgg_echo(string $message_key, array $args=[], string $language= '')
Elgg language module Functions to manage language and translations.
Definition: languages.php:17
isCompleted()
Check if upgrade has completed.
Definition: Loop.php:237
trait Loggable
Enables adding a logger.
Definition: Loggable.php:14
$error
Bad request error.
Definition: 400.php:6
Logger.
Definition: Logger.php:25
$dt
Definition: time.php:70
getBatch()
Return instance of the class that processes the data.
const UNKNOWN_COUNT
countItems() should return this if it doesn&#39;t know how many items remain.
Definition: Batch.php:15
Represents an upgrade that runs outside of the upgrade.php script.
Definition: ElggUpgrade.php:26
CLI Progress reporter.
Definition: Progress.php:11
Result of a single BatchUpgrade run.
Definition: Result.php:10
elgg_register_error_message(string|array $options)
Registers a error system message.
Definition: elgglib.php:62
getLogger()
Returns logger.
Definition: Loggable.php:37
$ts
CSRF security token view for use with secure forms.
getDisplayName()
{}
report()
Report loop results.
Definition: Loop.php:182
loop($max_duration=null)
Run upgrade loop for a preset number of seconds.
Definition: Loop.php:102
Upgrade loop Executes upgrade batches for a given duration of time.
Definition: Loop.php:15