Pular para o conteúdo principal

Gluo vs Laravel vs Symfony

The same small full-stack application built with byjg/gluo, Laravel and Symfony, each one following what its own framework recommends, to compare how much each framework gives you and how each feature is actually done.

Symfony is built twice: once with official packages only, and once on the community Docker template its documentation points to. Gluo and Laravel ship their own Docker setup and soft delete, so one build of each covers both.

The application​

Logina login screen; every product screen requires an authenticated user
Migrationa products table: id, name varchar(100), price decimal(10,2), created_at, updated_at, deleted_at
CRUDlist, create, edit and soft-delete a product, from the UI
DatabaseMySQL 8 in Docker
Seed user[email protected] / !P4ssw0rdstr!

The builds​

DirectoryStackOpenMySQL portStep-by-step guide
gluo/byjg/gluo 7.0, MicroOrm (ActiveRecord), JWT API + React SPAhttp://localhost:7080 or https://localhost:7443 (API and Swagger on :8080)3306docs/gluo.md
laravel/Laravel 13, Eloquent, React starter kit (Inertia + Fortify), Sailhttp://localhost:80813307docs/laravel.md
symfony-official/Symfony 8.1, Doctrine ORM, Twig, form login; official packages only, PHP on the host, MySQL in Dockerhttp://localhost:8083/product3309docs/symfony-official.md
symfony/the same, on the Symfony Docker template (FrankenPHP) with the Gedmo soft-delete bundlehttp://localhost:8082/product3308docs/symfony.md

Each guide lists every command and every hand edit, so any of them can be rebuilt from an empty directory. The ports do not overlap, so they can all run side by side.

Side by side​

How each feature is done, in short. docs/comparison.md has the same table expanded, with the actual code from each project. Where the two Symfony builds differ, the cell shows both: official is symfony-official/, template is symfony/.

FeatureGluoLaravelSymfony
Create the projectcomposer create-project byjg/gluo, answers in setup.jsoncomposer create-project laravel/react-starter-kitofficial: composer create-project symfony/skeleton + composer require webapp. template: clone Symfony Docker, then composer require webapp
ArchitectureJSON API + separate React SPAone app; React pages rendered through Inertiaone app; server-rendered Twig
Loginshipped: POST /login returns a JWT; SPA login screenshipped: Fortify session login; React login screengenerated: make:user + make:security:form-login
Protect a screen#[RequireAuthenticated] / #[RequireRole] on the methodauth middleware on the route groupaccess_control in security.yaml
Migrationhand-written SQL, up and downPHP schema builder, written by handSQL generated by diffing the entity
Modelgenerated from the tablewritten by hand; columns read at runtimegenerated by make:entity, with accessors
Model patternActiveRecord or Repository, chosen per entity; both generated (ActiveRecord used here)ActiveRecord onlyData Mapper + Repository only
Soft delete + timestampstraits, added by codegen from the column namesSoftDeletes trait + softDeletes() in the migrationofficial: lifecycle callbacks + a hand-written SQL filter. template: community bundle (Gedmo) + 2 traits + 2 YAML blocks
CRUD controllergenerated, working (get, list, create, update)generated empty; bodies by handgenerated, working (all five actions)
ValidationOpenAPI attributes on the model and the endpoint, #[ValidateRequest]; types, required fields and lengths generated from the table, value ranges by handFormRequest rules, by hand#[Assert\...] on the entity, by hand
Routing#[OA\Get] attributes compiled into openapi.jsonroutes/web.php#[Route] attributes
ScreensReact page written by handReact pages written by handTwig pages generated by make:crud
API documentationOpenAPI + Swagger UI, always in sync with the routesnonenone
Testsgenerated, passing; checked against the OpenAPI contractwritten by handgenerated as placeholders; rewritten
Command line./gluo (migrate, codegen, openapi, your own commands)php artisanbin/console
HTTPS in developmentfrontend: yes, self-signed certificate generated at start; API: HTTPno (Sail serves HTTP)official: no with php -S. template: yes, Caddy's local certificate authority (turned off in this build)
Dockerdocker-compose.yml shippedSail, one commandofficial: Flex writes the database service only; PHP runs on the host. template: full stack in Docker; both default to PostgreSQL, MySQL is a manual switch

Where things live​

The first-level folders of each project and their equivalents. The full table, with notes, is in docs/comparison.md.

PurposeGluoLaravelSymfony
Application codesrc/app/src/
Migrationsdb/ — plain SQL filesdatabase/migrations/ — PHP classes using the schema buildermigrations/ — PHP classes wrapping generated SQL
Seed datadb/base.sqldatabase/seeders/, database/factories/src/DataFixtures/
Configurationconfig/<env>/*.phpconfig/*.php, .envconfig/packages/*.yaml, .env
Routes— (attributes, compiled to public/docs/openapi.json)routes/— (attributes)
Screensfrontend/ (separate package)resources/templates/, assets/
Command linegluoartisanbin/console
Cache and logs— (system temp)storage/, bootstrap/cache/var/
Dockerdocker/, docker-compose.ymlcompose.yaml (images in vendor/laravel/sail)compose.yaml; the template adds Dockerfile, frankenphp/
Teststests/tests/tests/

What was generated vs written by hand​

Lines added or changed by hand for the Product feature, excluding tests, seed data and blank lines. Everything else came from a command.

BackendScreensConfig
GluoSQL migration 10, delete endpoint ~20, price range 21 React page + route + menu (~135)none
Laravelmigration columns 3, model 2, rules 2, controller ~30, route 12 React pages + type + menu (~140).env ports
Symfony (official)entity ~15, delete action 1, SQL filter 21none (generated)~7 lines of YAML, the MySQL switch in 3 files
Symfony (template)entity 7none (generated)~12 lines of YAML, the MySQL switch in 4 files, 1 template fix
Composer packages (prod + dev)Tests in the project
Gluo56 + 2542
Laravel100 + 3847
Symfony (official)96 + 358
Symfony (template)98 + 358

Can the code be generated from the model or the table?​

DirectionWhat the generator writesWhat is left to write
Gluo ./gluo codegen:alltable -> codeworking model, controller (get, list, create, update), tests, OpenAPISQL migration, delete endpoint, frontend screen
Symfony make:entity + make:crud + make:migrationentity -> code and SQLentity with accessors, working controller, form, 6 Twig screens, migration SQLvalidation, timestamps/soft delete wiring, access control, real tests
Laravel make:model -mfcrmodel -> migration -> tableempty skeletons (model, migration, factory, controller)columns, rules, controller bodies, React screens, tests

Only Gluo starts from an existing table. Symfony starts from the entity class and derives the SQL. Laravel has no first-party generator that fills in a CRUD.

Run them​

Dependencies are not in the repository, so each project is installed first. From a fresh clone:

# Gluo: API on :8080, frontend on :7080 and https://localhost:7443
cd gluo && composer install && docker compose up -d && ./gluo --env=dev migrate:reset

# Laravel: http://localhost:8081
cd laravel && composer install && cp .env.example .env && php artisan key:generate
./vendor/bin/sail up -d
./vendor/bin/sail npm install && ./vendor/bin/sail npm run build
./vendor/bin/sail artisan migrate:fresh --seed

# Symfony, official packages only: http://localhost:8083/product
cd symfony-official && composer install && docker compose up -d --wait
php bin/console doctrine:migrations:migrate -n && php bin/console doctrine:fixtures:load -n
php -S 0.0.0.0:8083 -t public

# Symfony, Docker template: http://localhost:8082/product
# (the container installs the dependencies and runs the migrations when it starts)
cd symfony && docker compose up -d --wait
docker compose exec php bin/console doctrine:fixtures:load -n

Log in with [email protected] / !P4ssw0rdstr!. Each guide has the commands for the tests and for shutting down.

The environment files in the repository hold generated development secrets and placeholder credentials only. None of these projects is meant to be deployed as is.