Elgg  Version master
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 $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($max_duration = null): void {
72 
73  $started = microtime(true);
74 
75  $this->upgrade->setStartTime();
76  $progress = $this->progress->start($this->upgrade->getDisplayName(), $this->count);
77 
78  while ($this->canContinue($started, $max_duration)) {
79  $this->runBatch($progress);
80  }
81 
82  $this->progress->finish($progress);
83 
84  $this->upgrade->processed = $this->processed;
85  $this->upgrade->offset = $this->offset;
86 
87  if (!$this->isCompleted()) {
88  return;
89  }
90 
91  // Upgrade is finished
92  if ($this->result->getFailureCount()) {
93  // The upgrade was finished with errors. Reset offset
94  // and errors so the upgrade can start from a scratch
95  // if attempted to run again.
96  $this->upgrade->processed = 0;
97  $this->upgrade->offset = 0;
98  } else {
99  // Everything has been processed without errors
100  // so the upgrade can be marked as completed.
101  $this->upgrade->setCompleted();
102  $this->result->markComplete();
103  }
104 
105  $this->report();
106  }
107 
115  protected function runBatch(ProgressBar $progress): void {
116  try {
117  $this->batch->run($this->result, $this->offset);
118  } catch (\Exception $e) {
119  $this->getLogger()->error($e);
120 
121  $this->result->addError($e->getMessage());
122  $this->result->addFailures(1);
123  }
124 
125  $failure_count = $this->result->getFailureCount();
126  $success_count = $this->result->getSuccessCount();
127 
128  $total = $this->upgrade->processed + $failure_count + $success_count;
129 
130  $progress->advance($total - $this->processed);
131 
132  if ($this->batch->needsIncrementOffset()) {
133  // Offset needs to incremented by the total amount of processed
134  // items so the upgrade we won't get stuck upgrading the same
135  // items over and over.
136  $this->offset = $total;
137  } else {
138  // Offset doesn't need to be incremented, so we mark only
139  // the items that caused a failure.
140  $this->offset = $this->upgrade->offset + $failure_count;
141  }
142 
143  $this->processed = $total;
144  }
145 
151  protected function report(): void {
152  $upgrade_name = $this->upgrade->getDisplayName();
153 
154  if ($this->upgrade->isCompleted()) {
155  $ts = $this->upgrade->getCompletedTime();
156  $dt = new \DateTime();
157  $dt->setTimestamp((int) $ts);
158  $format = elgg_get_config('date_format') ?: DATE_ATOM;
159 
160  if ($this->result->getFailureCount()) {
161  elgg_register_error_message(elgg_echo('admin:upgrades:completed:errors', [
162  $upgrade_name,
163  $dt->format($format),
164  $this->result->getFailureCount(),
165  ]));
166  } else {
167  elgg_register_success_message(elgg_echo('admin:upgrades:completed', [
168  $upgrade_name,
169  $dt->format($format),
170  ]));
171  }
172  } else {
173  elgg_register_error_message(elgg_echo('admin:upgrades:failed', [$upgrade_name]));
174  }
175 
176  foreach ($this->result->getErrors() as $error) {
177  $this->getLogger()->error($error);
178  }
179  }
180 
189  protected function canContinue($started, $max_duration = null): bool {
190  if (!isset($max_duration)) {
191  $max_duration = elgg_get_config('batch_run_time_in_secs');
192  }
193 
194  if ($max_duration && (microtime(true) - $started) >= $max_duration) {
195  return false;
196  }
197 
198  return !$this->isCompleted();
199  }
200 
206  protected function isCompleted(): bool {
207  if ($this->batch->shouldBeSkipped()) {
208  return true;
209  }
210 
211  if ($this->result->wasMarkedComplete()) {
212  return true;
213  }
214 
215  if ($this->count === Batch::UNKNOWN_COUNT) {
216  // the batch reports an unknown count and should mark the Result as complete when it's done
217  return false;
218  }
219 
220  if (!$this->batch->needsIncrementOffset()) {
221  // the batch has some way of marking progress (like a delete) and the count items should reflect this
222  return ($this->batch->countItems() - $this->result->getFailureCount()) <= 0;
223  }
224 
225  return $this->processed >= $this->count;
226  }
227 }
$format
Definition: date.php:37
$started
Definition: completed.php:15
int $processed
Definition: Loop.php:28
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.
canContinue($started, $max_duration=null)
Check if the loop cand and should continue.
Definition: Loop.php:189
runBatch(ProgressBar $progress)
Run batch.
Definition: Loop.php:115
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:206
int $max_duration
Definition: Loop.php:24
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.
if(empty($guid)) $upgrade
Definition: upgrade.php:11
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
setLogger(LoggerInterface $logger=null)
Set (or remove) the logger.
Definition: Loggable.php:28
__construct(protected\ElggUpgrade $upgrade, protected Result $result, protected Progress $progress, Logger $logger)
Constructor.
Definition: Loop.php:42
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:151
loop($max_duration=null)
Run upgrade loop for a preset number of seconds.
Definition: Loop.php:71
Upgrade loop Executes upgrade batches for a given duration of time.
Definition: Loop.php:15