3v4l.org

run code in 300+ PHP versions simultaneously
<?php exit; namespace Foobar; use Foobar\Packer\Internal; use PhpParser\ParserFactory; use PhpParser\Node\Stmt; class Packer { private $compactSourceFormat; function __construct($a = false) { $this->compactSourceFormat = $a; } function pack(string ...$a): string { $b = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); $c = $this->compactSourceFormat ? new Internal\PrintCompact() : new Internal\PrintStandard(); $d = new Internal\Pruner(); $e = []; foreach ($a as $f) { $g = $b->parse($f); if (!$g) continue; if (!$g[0] instanceof Stmt\Namespace_) $g = [new Stmt\Namespace_(null, $g)]; array_push($e, ...$g); } $d->process($e); return $c->prettyPrint($e); } } namespace Foobar\Packer\Internal; class Utils { static function getSymbolGen($a, $b) { $c = 0; $d = strlen($a); return function () use (&$c, $a, $d, $b) { do { $e = ''; $f = $c; do { $e .= $a[$f % $d]; $f = (int) ($f / $d); } while ($f); $c++; } while (isset($b[$e])); return $e; }; } } namespace Foobar\Packer\Internal; use PhpParser\Node; class Traverser { const SKIP_CHILDREN = 1; const ABORT = 2; const REMOVE = 3; protected $before, $after; function __construct(?\Closure $a, ?\Closure $b = null) { $this->before = $a; $this->after = $b; } function traverse(&$a): void { $b = $this->before; $c = $this->after; $d = static function (&$e, $f) use ($b, $c, &$d, &$g) { $h = []; foreach ($e as $i => &$j) if (is_array($j)) $d($j, $f); elseif ($j instanceof Node) { $k = $g($j, $f); if ($k === self::REMOVE) $h[] = [$i, null]; elseif ($k) $h[] = [$i, $k]; } if ($h) do { [$i, $l] = array_pop($h); if ($l === null) \array_splice($e, $i, 1); elseif ($l instanceof Node) \array_splice($e, $i, 1, [$l]); else \array_splice($e, $i, 1, $l); } while ($h); }; $g = static function (Node &$m, $f) use ($b, $c, &$d, &$g) { $n = null; if ($b) $n = $b($m, $f); if ($n === self::ABORT) throw new TraverserAbort(); if ($n === null) foreach ($m->getSubNodeNames() as $o) { $p = &$m->{$o}; if (is_array($p)) $d($p, $m); elseif ($p instanceof Node) { $k = $g($p, $m); if ($k === self::ABORT) $p = null; elseif ($k instanceof Node) $p = $k; else if ($k) throw new \Exception('The node can\'t be replaced with a list of nodes, as it\'s not located in a list context.'); } } $q = null; if ($c) $q = $c($m, $f); if ($q === self::ABORT) throw new TraverserAbort(); if ($n || $q) { if ($q === self::REMOVE || $n === self::REMOVE) return self::REMOVE; if ($q instanceof Node || \is_array($q)) return $q; if ($n instanceof Node || \is_array($n)) return $n; if ($n !== self::SKIP_CHILDREN) throw new \Exception('Invalid return value from $before callback.'); if ($q !== self::SKIP_CHILDREN) throw new \Exception('Invalid return value from $after callback.'); } }; try { if (is_array($a)) $d($a, null); elseif ($a instanceof Node) $g($a, null); else throw new \TypeError('Unexpected format for $nodeOrNodes. Array or Node instance expected.'); } catch (TraverserAbort $e) {} } } namespace Foobar\Packer\Internal; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Stmt; use PhpParser\Comment; class Pruner { private const VAR_RESERVED = ['this' => true, '_ENV' => true, '_GET' => true, '_POST' => true, '_FILES' => true, '_SERVER' => true, '_COOKIE' => true, '_REQUEST' => true, '_SESSION' => true, 'GLOBALS' => true]; private const VAR_DICTIONARY = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; private $varGenMap, $varGenClosure; function process(&$a): void { $b = $this->getTraverser(); $b->traverse($a); } private function getTraverser() { $a = 0; $this->varGenReset(); $b = new Traverser( function (Node &$c) use (&$a) { if ($c instanceof Node\FunctionLike) $a++; }, function (Node &$c) use (&$a) { if ($c instanceof Node\FunctionLike) { $a--; if ($a == 0) $this->varGenReset(); } if ($a > 0 && $c instanceof Expr\Variable || $c instanceof Node\Param) if (is_string($c->name)) $c->name = $this->varGen($c->name); if ($a > 0 && $c instanceof Expr\ClosureUse) $c->var = $this->varGen($c->var); if ($c instanceof Stmt\Interface_) $c->stmts = []; if ($c instanceof Stmt\ClassMethod && $c->flags & Stmt\Class_::MODIFIER_ABSTRACT) return NodeTraverser::REMOVE_NODE; if ($c->getAttribute('comments')) $c->setAttribute('comments', null); if ($c instanceof Expr\FuncCall) if ($c->name instanceof Node\Name && $c->name->toString() === 'assert') return Traverser::REMOVE; if ($c instanceof Stmt\If_ && !$c->else && !$c->elseifs) { $d = $c->cond; if ($d instanceof Expr\BinaryOp\BooleanAnd) $d = $d->left; if ($d instanceof Expr\ConstFetch && $d->name->toString() === 'DEBUG') return Traverser::REMOVE; } if ($c instanceof Stmt\Class_) $c->flags &= ~(Stmt\Class_::MODIFIER_ABSTRACT | Stmt\Class_::MODIFIER_FINAL); if ($c instanceof Stmt\ClassMethod) $c->flags &= ~Stmt\Class_::MODIFIER_PUBLIC; }); return $b; } protected function varGenReset() { $this->varGenMap = []; $this->varGenClosure = Utils::getSymbolGen(self::VAR_DICTIONARY, self::VAR_RESERVED); } protected function varGen($a) { if (isset(self::VAR_RESERVED[$a])) return $a; if (!isset($this->varGenMap[$a])) { $b = ($this->varGenClosure)(); $this->varGenMap[$a] = $b; } return $this->varGenMap[$a]; } } namespace Foobar\Packer\Internal; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\Node\Stmt; class Compactor { private const CONST_USE = Stmt\Use_::TYPE_CONSTANT; private const FUNC_USE = Stmt\Use_::TYPE_FUNCTION; private const NORM_USE = Stmt\Use_::TYPE_NORMAL; function __construct($a) { $b = []; $c = []; foreach ($a as $d) { $e = $d->name === null ? 'global' : $d->name->toString(); $f = $this->getAliased($d); $g = $this->getMentioned($d); $h = []; $i = []; $j = []; foreach ([self::CONST_USE, self::FUNC_USE, self::NORM_USE] as $k) { $h[$k] = \array_diff_key($g[$k], $f[$k]); $f[$k] = \array_intersect_key($f[$k], $g[$k]); } $l = ['node' => &$d, 'aliased' => $f, 'original' => $h, 'mentioned' => $g]; $b[] = $l; $c[$e]['nodes'][] = &$d; $c[$e]['original'][Stmt\Use_::TYPE_NORMAL] += $h[Stmt\Use_::TYPE_NORMAL]; $c[$e]['original'][Stmt\Use_::TYPE_FUNCTION] += $h[Stmt\Use_::TYPE_FUNCTION]; $c[$e]['original'][Stmt\Use_::TYPE_CONSTANT] += $h[Stmt\Use_::TYPE_CONSTANT]; $c[$e]['mentioned'][Stmt\Use_::TYPE_NORMAL] += $g[Stmt\Use_::TYPE_NORMAL]; $c[$e]['mentioned'][Stmt\Use_::TYPE_FUNCTION] += $g[Stmt\Use_::TYPE_FUNCTION]; $c[$e]['mentioned'][Stmt\Use_::TYPE_CONSTANT] += $g[Stmt\Use_::TYPE_CONSTANT]; } } private function getAliased(Stmt\Namespace_ $a) { $b = []; foreach ($a->stmts as $c) { if (!$c instanceof Stmt\Use_ && !$c instanceof Stmt\GroupUse) break; $d = $c instanceof Stmt\GroupUse; $e = $d ? $c->prefix->toString() . '\\' : ''; foreach ($c->uses as $f) { $g = $e . $f->name->toString(); $h = $f->alias; $i = $d ? $f->type : $c->type; $j = $i !== Stmt\Use_::TYPE_CONSTANT; $k[$i][$j ? \strtolower($h) : $h] = $g; } } return $b; } private function getMentioned(Stmt\Namespace_ $a) { $b = []; $c = new Traverser( function ($d, $e) use (&$b) { if ($d instanceof Stmt\Const_) foreach ($d->consts as $f) $b[self::CONST_USE][$f->name] = true; if ($d instanceof Stmt\Function_) $b[self::FUNC_USE][\strtolower($d->name)] = true; if ($d instanceof Stmt\ClassLike) if ($d->name !== null) $b[self::NORM_USE][\strtolower($d->name)] = true; if ($d instanceof Node\Name) { $g = $d->toString(); if ($e instanceof Stmt\UseUse) ; elseif ($e instanceof Expr\ConstFetch) $b[self::CONST_USE][$g] = true; elseif ($e instanceof Expr\FuncCall) $b[self::FUNC_USE][\strtolower($g)] = true; else $b[self::NORM_USE][\strtolower($g)] = true; } }); $c->traverse($a); return $b; } } 135.6968879699707 Before: 21364 bytes; After: 8523 bytes Before: 566 lines; After: 244 lines // 135.6968879699707 ms // Before: 21364 bytes; After: 8523 bytes // Before: 566 lines; After: 244 lines

Here you find the average performance (time & memory) of each version. A grayed out version indicates it didn't complete successfully (based on exit-code).

VersionSystem time (s)User time (s)Memory (MiB)
7.1.70.0030.01315.21
7.1.60.0230.01333.31
7.1.50.0170.02333.08
7.1.40.4430.01732.69
7.1.30.0170.02032.50
7.1.20.0270.01732.72
7.1.10.0030.01314.47
7.1.00.0270.02014.55
7.0.200.0070.01714.79
7.0.190.0030.01714.52
7.0.180.0230.00714.16
7.0.170.0000.01714.20
7.0.160.4000.01014.28
7.0.150.0130.00714.43
7.0.140.2970.00714.21
7.0.130.0100.01014.45
7.0.120.0100.00714.47
7.0.110.3430.01314.51
7.0.100.2400.01714.45
7.0.90.0270.00714.30
7.0.80.3130.00714.25
7.0.70.0670.01714.21
7.0.60.3030.00714.02
7.0.50.0070.01314.29
7.0.40.0130.01014.37
7.0.30.0030.01314.45
7.0.20.0030.01314.32
7.0.10.0230.00314.38
7.0.00.0030.01314.32

preferences:
134.31 ms | 1394 KiB | 7 Q