zendframework/zend-mvc-plugin-prg
The Post/Redirect/Get plugin for zend-mvc prevents duplicate form submissions by redirecting after a successful POST. Configure it to redirect to a route or URL, keeping form data available for the next request.
This is a Zend Framework / Laminas MVC component (zendframework/zend-mvc-plugin-prg), not a Laravel package — it ships the PostRedirectGet (PRG) controller plugin for zend-mvc. In a Laravel codebase you will almost never composer require it; you meet it when you inherit or maintain a ZF/Laminas app, or when you are porting PRG behaviour between the two frameworks. Treat 1.2.0 as the "current" line and know its constraints before touching composer.json.
Minimal steps:
composer require zendframework/zend-mvc-plugin-prg:^1.2 — this now implies PHP >= 7.3 and zend-stdlib ^3 (v2 support was removed in this release).Zend\Mvc\Plugin\Prg\PostRedirectGet (the plugin itself) and Zend\Mvc\Plugin\Prg\FilePrg (the file-upload variant). Those two classes are the whole surface area.Zend\Mvc\Plugin\Prg must be in config/modules.config.php (or application.config.php) so the prg and fileprg controller plugin aliases are registered.public function createAction()
{
$prg = $this->prg(); // returns Response on POST, or the validated data on GET-redirect
if ($prg instanceof Response) {
return $prg; // 303 redirect already issued
}
if ($prg === false) {
return new ViewModel(['form' => $form]); // first GET
}
// $prg is the POST data, replayed on the redirected GET
$form->setData($prg);
// ...persist, then redirect to a success route
}
The Laravel equivalent you already know is return redirect()->route('x')->withInput() plus old() — PRG-in-a-plugin is the same idea, but the framework does the round-trip for you.
Controller-level PRG (the canonical pattern). Call $this->prg() at the top of the action, branch on Response / false / array. The plugin stashes POST data in the session, issues a 303, and hands the data back on the subsequent GET so the form can be re-populated. This is the pattern to copy if you are reimplementing it in Laravel:
// Laravel analogue of the ZF plugin's contract
$data = session()->pull('prg.data'); // replay once
if ($request->isMethod('post')) {
session()->put('prg.data', $request->validated());
return redirect()->route($request->route()->getName());
}
Explicit redirect target. Pass a route name or URL as the first argument ($this->prg('event/thanks')) when the default "same URL" redirect is wrong — e.g. after a multi-step wizard, always land on the summary route, not the step route.
File uploads. Use $this->fileprg($form) instead of prg when the POST contains files. It rehydrates the form with the uploaded files on the redirected GET, which is the one place PRG genuinely earns its keep versus a hand-rolled withInput() (Laravel's withInput() cannot carry UploadedFile objects across a redirect).
Bridging into Laravel (integration tip). If you must run both stacks in one repo (legacy ZF admin + Laravel app), bind the PRG semantics rather than the plugin:
// AppServiceProvider — adapt ZF's session-backed PRG store to Laravel's session
$this->app->bind(\Zend\Mvc\Plugin\Prg\PostRedirectGet::class, function ($app) {
return new \Zend\Mvc\Plugin\Prg\PostRedirectGet(
$app['session.store']->driver() // must satisfy Zend\Session\Storage\StorageInterface
);
});
Be honest with yourself: the StorageInterface mismatch (zf-session vs Laravel's Store) means you usually end up writing a thin adapter or simply porting the 30-line logic instead. Porting is normally cheaper.
Extension points worth knowing. Override the redirect behaviour by extending PostRedirectGet and overriding redirectTo(); swap the session backend with setSessionManager(). Plugin aliases (prg, fileprg) are set in the module's Module::getConfig(), so a custom subclass is registered by aliasing over them in your own module config — not by editing vendor code.
composer.lock still pins zend-stdlib:^2.x (common in older ZF apps), composer update will fail or force a wider upgrade. Run composer why zendframework/zend-stdlib before bumping; expect fallout in zend-form, zend-filter and friends that share the same constraint.^1.1 there.false). Don't treat the returned array as durable storage; persist to your repository before redirecting.false vs empty array. $prg === false means "not a PRG request"; an empty array means "PRG round-trip with no fields". Loose comparisons against false will silently swallow the second case — always use ===.config/ file. There is nothing to publish; all configuration is ZF module config. Autodiscovery will not register anything in a Laravel app, so any assumption of Laravel-style package ergonomics here is wrong.laminas/laminas-mvc-plugin-prg when Zend Framework moved to the Laminas project. 1.2.0 is the last stretch of the Zend-named line; new work should target Laminas, and the class names change (Zend\Mvc\Plugin\Prg → Laminas\Mvc\Plugin\Prg).$request->isPost(). Most "plugin does nothing" reports are a missing Zend\Session config, not a plugin bug.validate() + redirect()->route(...)->withInput() + old(). It is idiomatic, testable with assertSessionHasInput, and needs no cross-framework session adapter. Reach for this package only inside ZF/Laminas MVC.How can I help you explore Laravel packages today?