loicpennamen/sf-front-editor
sf-front-editor is a PHP package that adds an in-place front-end editing layer to your application, letting authorized users update page content directly in the browser. It simplifies copy changes and reduces reliance on backend admin screens.
Reality check first: this is not a Laravel package, and it is probably not a modern Composer package either. The sf- prefix is Symfony 1.x naming (sfFrontEditorPlugin), which predates Composer entirely and corresponds to a framework that reached end-of-life in 2012. Before you write a line of integration code, confirm what you actually cloned:
git clone https://bitbucket.org/LoicPennamen/sf-front-editor.git
cd sf-front-editor
ls -la
cat package.xml 2>/dev/null # Symfony 1 plugin manifest → plugin:install
cat composer.json 2>/dev/null # Symfony 2+ bundle → VCS repository entry
cat README* 2>/dev/null
If you see package.xml and config/*PluginConfiguration.class.php, it is a Symfony 1 plugin and the install path is php symfony plugin:install — nothing about it will run under Laravel. If you see composer.json, it is a Symfony 2+ bundle; you would install it via a VCS repository entry and only inside a Symfony app.
For a Laravel codebase, treat the "install" step as reading the source, not requiring it. Where to look, in order:
web/js/, web/css/). These may be reusable independently of Symfony, which is the only part with real day-to-day value.The first use case to validate, if you are porting the pattern into Laravel: one editable text region on one Blade-rendered page. Click it, change the text, hit save, reload, see it persisted. If you cannot get that round-trip working in an afternoon with your own thin implementation, the package was never the bottleneck — your content model was.
If you are actually on a Symfony 1 project (rare but possible), install is:
php symfony plugin:install sfFrontEditorPlugin
php symfony plugin:publish-assets
php symfony cc
Every in-place front-end editor follows the same three-part shape, and this one will too: a server-side helper emits a wrapper element with data attributes, a JavaScript layer promotes that element into an editable surface, and an AJAX save posts the new value back to a controller action. The port to Laravel is a port of that shape, not of the code. Concretely:
1. Own the render seam. Do not call the package's helper from Blade even if you could shim it. Write your own Blade component or directive and decide its public API yourself:
<x-editable field="hero.body" :model="$page" culture="{{ app()->getLocale() }}">
{!! $page->hero_body !!}
</x-editable>
The component renders the same data attributes the Symfony helper does (a target, a field key, a locale), but you control the markup. That gives you one place to add sanitization, permission checks, and cache-busting later — the single most important decision in this whole assessment.
2. Route saves through a service, not a controller-to-model write. The Symfony module almost certainly writes directly to the content column. Do not reproduce that. In Laravel:
// app/Actions/UpdateContentBlock.php
public function handle(Page $page, string $field, string $value, User $editor): ContentRevision
{
return DB::transaction(function () use ($page, $field, $value, $editor) {
$revision = ContentRevision::create([
'page_id' => $page->id, 'field' => $field,
'old' => $page->{$field}, 'new' => $value,
'user_id' => $editor->id,
]);
$page->update([$field => $value]);
return $revision;
});
}
Inline editing generates many small unreviewed writes. An audit table is cheap now and impossible to retrofit once editors are used to instant saves.
3. Gate edit mode in middleware, and check one flag in templates. The Symfony original likely gates on sfContext::getInstance()->getUser()->isAuthenticated(). In Laravel, resolve an editing flag once in a middleware or Gate, share it with Inertia::share/View::share, and let the Blade component and the JS entry point both read that single source of truth. Never scatter @if(auth()->user()->can(...)) across views — that is exactly how these integrations rot.
4. Keep the JS layer thin and framework-agnostic. If the package ships usable JS (a contenteditable wrapper plus a fetch call), extract it, replace the save URL with a configurable route, and drop it into your Vite pipeline. That is the one piece that survives the port verbatim. Bind it to a PATCH /api/content/{block} endpoint behind auth:sanctum and can:update-content — an API contract you own, not Symfony's route.
5. Wire editor-mode into your cache strategy from day one. Front-end editing means writes land next to the render path. Wrap page reads in your cache layer, and bust the specific page's key inside the UpdateContentBlock action above. Do this in the same PR as the feature — retrofitting cache invalidation onto a live inline editor is how you get editors seeing stale text and losing confidence in the tool.
sf- means Symfony 1, not Symfony 2. If you were handed this as a "Symfony bundle," verify the version first. A Symfony 1 plugin has no Composer support, no namespaces, and no upgrade path. Do not build a roadmap on it.mews/purifier or symfony/html-sanitizer via an adapter) before persistence and escape on output. Stored XSS via an inline editor is a content-team-facing security incident.auth.page_content table keyed by (page, field, culture). Preserve that key shape if you port the schema; dropping it means a painful migration the first time marketing needs a second language.plugin:publish-assets is a separate step and forgetting it produces a silent "click does nothing" — the markup is there, the JS 404s. Check the browser network tab before debugging PHP.executeSave (or the equivalent) rather than editing vendor files — that is where you add transactions, revisioning, and cache invalidation. Everything else in the package is cosmetic.How can I help you explore Laravel packages today?