Elgg  Version master
Loop.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Upgrade;
4 
7 use Elgg\Logger;
8 use Elgg\Traits\Loggable;
9 use Symfony\Component\Console\Helper\ProgressBar;
10 
15 class Loop {
16 
17  use Loggable;
18 
22  protected $batch;
23 
24  protected int $max_duration;
25 
26  protected int $count;
27 
28  protected int $processed;
29 
30  protected int $offset;
31 
42  public function __construct(
43  protected \ElggUpgrade $upgrade,
44  protected Result $result,
45  protected Progress $progress,
46  Logger $logger
47  ) {
48  $this->setLogger($logger);
49 
50  // Get the class taking care of the actual upgrading
51  $this->batch = $upgrade->getBatch();
52  if (!$this->batch) {
53  throw new RuntimeException(elgg_echo('admin:upgrades:error:invalid_batch', [
54  $upgrade->getDisplayName(),
55  $upgrade->guid
56  ]));
57  }
58 
59  $this->count = $this->batch->countItems();
60  $this->processed = (int) $upgrade->processed;
61  $this->offset = (int) $upgrade->offset;
62  }
63 
71  public function loop(?int $max_duration = null): void {
72  $started = microtime(true);
73 
74  $this->upgrade->setStartTime();
75  $progress = $this->progress->start($this->upgrade->getDisplayName(), $this->count);
76 
77  while ($this->canContinue($started, $max_duration)) {
78  $this->runBatch($progress);
79  }
80 
81  $this->progress->finish($progress);
82 
83  $this->upgrade->processed = $this->processed;
84  $this->upgrade->offset = $this->offset;
85 
86  if (!$this->isCompleted()) {
87  return;
88  }
89 
90  // Upgrade is finished
91  if ($this->result->getFailureCount()) {
92  // The upgrade was finished with errors. Reset offset
93  // and errors so the upgrade can start from a scratch
94  // if attempted to run again.
95  $this->upgrade->processed = 0;
96  $this->upgrade->offset = 0;
97  } else {
98  // Everything has been processed without errors
99  // so the upgrade can be marked as completed.
100  $this->upgrade->setCompleted();
101  $this->result->markComplete();
102  }
103 
104  $this->report();
105  }
106 
114  protected function runBatch(ProgressBar $progress): void {
115  try {
116  $this->batch->run($this->result, $this->offset);
117  } catch (\Exception $e) {
118  $this->getLogger()->error($e);
119 
120  $this->result->addError($e->getMessage());
121  $this->result->addFailures(1);
122  }
123 
124  $failure_count = $this->result->getFailureCount();
125  $success_count = $this->result->getSuccessCount();
126 
127  $total = $this->upgrade->processed + $failure_count + $success_count;
128 
129  $progress->advance($total - $this->processed);
130 
131  if ($this->batch->needsIncrementOffset()) {
132  // Offset needs to incremented by the total amount of processed
133  // items so the upgrade we won't get stuck upgrading the same
134  // items over and over.
135  $this->offset = $total;
136  } else {
137  // Offset doesn't need to be incremented, so we mark only
138  // the items that caused a failure.
139  $this->offset = $this->upgrade->offset + $failure_count;
140  }
141 
142  $this->processed = $total;
143  }
144 
150  protected function report(): void {
151  $upgrade_name = $this->upgrade->getDisplayName();
152 
153  if ($this->upgrade->isCompleted()) {
154  $ts = $this->upgrade->getCompletedTime();
155  $dt = new \DateTime();
156  $dt->setTimestamp((int) $ts);
157  $format = elgg_get_config('date_format') ?: DATE_ATOM;
158 
159  if ($this->result->getFailureCount()) {
160  elgg_register_error_message(elgg_echo('admin:upgrades:completed:errors', [
161  $upgrade_name,
162  $dt->format($format),
163  $this->result->getFailureCount(),
164  ]));
165  } else {
166  elgg_register_success_message(elgg_echo('admin:upgrades:completed', [
167  $upgrade_name,
168  $dt->format($format),
169  ]));
170  }
171  } else {
172  elgg_register_error_message(elgg_echo('admin:upgrades:failed', [$upgrade_name]));
173  }
174 
175  foreach ($this->result->getErrors() as $error) {
176  $this->getLogger()->error($error);
177  }
178  }
179 
188  protected function canContinue($started, ?int $max_duration = null): bool {
189  if (!isset($max_duration)) {
190  $max_duration = (int) elgg_get_config('batch_run_time_in_secs');
191  }
192 
193  if ($max_duration > 0 && (microtime(true) - $started) >= $max_duration) {
194  return false;
195  }
196 
197  return !$this->isCompleted();
198  }
199 
205  protected function isCompleted(): bool {
206  if ($this->batch->shouldBeSkipped()) {
207  return true;
208  }
209 
210  if ($this->result->wasMarkedComplete()) {
211  return true;
212  }
213 
214  if ($this->count === Batch::UNKNOWN_COUNT) {
215  // the batch reports an unknown count and should mark the Result as complete when it's done
216  return false;
217  }
218 
219  if (!$this->batch->needsIncrementOffset()) {
220  // the batch has some way of marking progress (like a delete) and the count items should reflect this
221  return ($this->batch->countItems() - $this->result->getFailureCount()) <= 0;
222  }
223 
224  return $this->processed >= $this->count;
225  }
226 }
$error
Bad request error.
Definition: 400.php:6
if(empty($guid)) $upgrade
Definition: upgrade.php:11
$count
Definition: ban.php:24
Represents an upgrade that runs outside of the upgrade.php script.
Definition: ElggUpgrade.php:26
CLI Progress reporter.
Definition: Progress.php:11
Exception thrown if an error which can only be found on runtime occurs.
Logger.
Definition: Logger.php:26
Upgrade loop Executes upgrade batches for a given duration of time.
Definition: Loop.php:15
loop(?int $max_duration=null)
Run upgrade loop for a preset number of seconds.
Definition: Loop.php:71
runBatch(ProgressBar $progress)
Run batch.
Definition: Loop.php:114
__construct(protected \ElggUpgrade $upgrade, protected Result $result, protected Progress $progress, Logger $logger)
Constructor.
Definition: Loop.php:42
canContinue($started, ?int $max_duration=null)
Check if the loop can and should continue.
Definition: Loop.php:188
int $processed
Definition: Loop.php:28
report()
Report loop results.
Definition: Loop.php:150
int $max_duration
Definition: Loop.php:24
isCompleted()
Check if upgrade has completed.
Definition: Loop.php:205
Result of a single BatchUpgrade run.
Definition: Result.php:10
$started
Definition: completed.php:15
elgg_get_config(string $name, $default=null)
Get an Elgg configuration value.
elgg_register_success_message(string|array $options)
Registers a success system message.
Definition: elgglib.php:43
elgg_register_error_message(string|array $options)
Registers a error system message.
Definition: elgglib.php:62
$format
Definition: date.php:37
$dt
Definition: time.php:70
elgg_echo(string $message_key, array $args=[], string $language='')
Elgg language module Functions to manage language and translations.
Definition: languages.php:17
try
Definition: login_as.php:33
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10
$ts
CSRF security token view for use with secure forms.