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(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 }
loop(int $max_duration=null)
Run upgrade loop for a preset number of seconds.
Definition: Loop.php:71
$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.
runBatch(ProgressBar $progress)
Run batch.
Definition: Loop.php:114
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:205
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:26
$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
canContinue($started, int $max_duration=null)
Check if the loop cand and should continue.
Definition: Loop.php:188
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:150
Upgrade loop Executes upgrade batches for a given duration of time.
Definition: Loop.php:15