[{"data":1,"prerenderedAt":1846},["ShallowReactive",2],{"navigation_docs_en":3,"-en-getting-started-coming-from-symfony":342,"-en-getting-started-coming-from-symfony-surround":1841},[4,45,61,96,132,166,196,217,241,265],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F10.reference",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F10.reference\u002F1.configuration","i-lucide-settings",{"title":57,"path":58,"stem":59,"icon":60},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F10.reference\u002F2.dependencies","i-lucide-package",{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[67,72,77,82,86,91],{"title":68,"path":69,"stem":70,"icon":71},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture","i-lucide-layers",{"title":73,"path":74,"stem":75,"icon":76},"Application lifecycle","\u002Fen\u002Fconcepts\u002Fapplication-lifecycle","en\u002F2.concepts\u002F2.application-lifecycle","i-lucide-power",{"title":78,"path":79,"stem":80,"icon":81},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F3.error-model","i-lucide-shield-alert",{"title":52,"path":83,"stem":84,"icon":85},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F4.configuration","i-lucide-settings-2",{"title":87,"path":88,"stem":89,"icon":90},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F5.codegen-pipeline","i-lucide-file-json",{"title":92,"path":93,"stem":94,"icon":95},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F6.design-patterns","i-lucide-puzzle",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":44},"HTTP & Routing","i-lucide-globe","\u002Fen\u002Fhttp","en\u002F3.http",[102,107,112,117,122,127],{"title":103,"path":104,"stem":105,"icon":106},"The server core","\u002Fen\u002Fhttp\u002Fserver","en\u002F3.http\u002F1.server","i-lucide-server",{"title":108,"path":109,"stem":110,"icon":111},"The Fiber engine","\u002Fen\u002Fhttp\u002Ffiber","en\u002F3.http\u002F2.fiber","i-lucide-zap",{"title":113,"path":114,"stem":115,"icon":116},"The chi engine","\u002Fen\u002Fhttp\u002Fchi","en\u002F3.http\u002F3.chi","i-lucide-route",{"title":118,"path":119,"stem":120,"icon":121},"Error responses","\u002Fen\u002Fhttp\u002Ferror-responses","en\u002F3.http\u002F4.error-responses","i-lucide-octagon-alert",{"title":123,"path":124,"stem":125,"icon":126},"Validation","\u002Fen\u002Fhttp\u002Fvalidation","en\u002F3.http\u002F5.validation","i-lucide-badge-check",{"title":128,"path":129,"stem":130,"icon":131},"Pagination","\u002Fen\u002Fhttp\u002Fpagination","en\u002F3.http\u002F6.pagination","i-lucide-list-ordered",{"title":133,"icon":134,"path":135,"stem":136,"children":137,"page":44},"Database","i-lucide-database","\u002Fen\u002Fdatabase","en\u002F4.database",[138,142,147,151,156,161],{"title":139,"path":140,"stem":141,"icon":134},"Overview","\u002Fen\u002Fdatabase\u002Foverview","en\u002F4.database\u002F1.overview",{"title":143,"path":144,"stem":145,"icon":146},"pgx","\u002Fen\u002Fdatabase\u002Fpgx","en\u002F4.database\u002F2.pgx","i-lucide-plug",{"title":148,"path":149,"stem":150,"icon":71},"bun","\u002Fen\u002Fdatabase\u002Fbun","en\u002F4.database\u002F3.bun",{"title":152,"path":153,"stem":154,"icon":155},"Transactions","\u002Fen\u002Fdatabase\u002Ftransactions","en\u002F4.database\u002F4.transactions","i-lucide-git-merge",{"title":157,"path":158,"stem":159,"icon":160},"Migrations","\u002Fen\u002Fdatabase\u002Fmigrations","en\u002F4.database\u002F5.migrations","i-lucide-file-stack",{"title":162,"path":163,"stem":164,"icon":165},"Seeders","\u002Fen\u002Fdatabase\u002Fseeding","en\u002F4.database\u002F6.seeding","i-lucide-sprout",{"title":167,"icon":168,"path":169,"stem":170,"children":171,"page":44},"Async & Background","i-lucide-workflow","\u002Fen\u002Fasync","en\u002F5.async",[172,176,181,186,191],{"title":173,"path":174,"stem":175,"icon":131},"Queue","\u002Fen\u002Fasync\u002Fqueue","en\u002F5.async\u002F1.queue",{"title":177,"path":178,"stem":179,"icon":180},"Events","\u002Fen\u002Fasync\u002Fevents","en\u002F5.async\u002F2.events","i-lucide-radio",{"title":182,"path":183,"stem":184,"icon":185},"Outbox","\u002Fen\u002Fasync\u002Foutbox","en\u002F5.async\u002F3.outbox","i-lucide-inbox",{"title":187,"path":188,"stem":189,"icon":190},"Scheduling","\u002Fen\u002Fasync\u002Fscheduler","en\u002F5.async\u002F4.scheduler","i-lucide-calendar-clock",{"title":192,"path":193,"stem":194,"icon":195},"Worker","\u002Fen\u002Fasync\u002Fworker","en\u002F5.async\u002F5.worker","i-lucide-cog",{"title":197,"icon":198,"path":199,"stem":200,"children":201,"page":44},"Security","i-lucide-shield-check","\u002Fen\u002Fsecurity","en\u002F6.security",[202,207,212],{"title":203,"path":204,"stem":205,"icon":206},"Authentication","\u002Fen\u002Fsecurity\u002Fauthentication","en\u002F6.security\u002F1.authentication","i-lucide-key-round",{"title":208,"path":209,"stem":210,"icon":211},"RBAC","\u002Fen\u002Fsecurity\u002Frbac","en\u002F6.security\u002F2.rbac","i-lucide-users",{"title":213,"path":214,"stem":215,"icon":216},"Policies","\u002Fen\u002Fsecurity\u002Fpolicies","en\u002F6.security\u002F3.policies","i-lucide-gavel",{"title":218,"icon":219,"path":220,"stem":221,"children":222,"page":44},"Observability","i-lucide-activity","\u002Fen\u002Fobservability","en\u002F7.observability",[223,226,231,236],{"title":139,"path":224,"stem":225,"icon":219},"\u002Fen\u002Fobservability\u002Foverview","en\u002F7.observability\u002F1.overview",{"title":227,"path":228,"stem":229,"icon":230},"Logging","\u002Fen\u002Fobservability\u002Flogging","en\u002F7.observability\u002F2.logging","i-lucide-scroll-text",{"title":232,"path":233,"stem":234,"icon":235},"OpenTelemetry","\u002Fen\u002Fobservability\u002Fopentelemetry","en\u002F7.observability\u002F3.opentelemetry","i-lucide-radar",{"title":237,"path":238,"stem":239,"icon":240},"Sentry","\u002Fen\u002Fobservability\u002Fsentry","en\u002F7.observability\u002F4.sentry","i-lucide-bug",{"title":242,"icon":243,"path":244,"stem":245,"children":246,"page":44},"Storage & Mail","i-lucide-hard-drive","\u002Fen\u002Fstorage-mail","en\u002F8.storage-mail",[247,251,256,261],{"title":248,"path":249,"stem":250,"icon":243},"Storage","\u002Fen\u002Fstorage-mail\u002Fstorage","en\u002F8.storage-mail\u002F1.storage",{"title":252,"path":253,"stem":254,"icon":255},"Email","\u002Fen\u002Fstorage-mail\u002Fmail","en\u002F8.storage-mail\u002F2.mail","i-lucide-mail",{"title":257,"path":258,"stem":259,"icon":260},"Notifications","\u002Fen\u002Fstorage-mail\u002Fnotifications","en\u002F8.storage-mail\u002F3.notifications","i-lucide-bell",{"title":262,"path":263,"stem":264,"icon":180},"Realtime","\u002Fen\u002Fstorage-mail\u002Frealtime","en\u002F8.storage-mail\u002F4.realtime",{"title":266,"icon":267,"path":268,"stem":269,"children":270,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F9.cli",[271,274,279,284,289,294,299,303,308,313,318,323,328,333,337],{"title":139,"path":272,"stem":273,"icon":267},"\u002Fen\u002Fcli\u002Foverview","en\u002F9.cli\u002F1.overview",{"title":275,"path":276,"stem":277,"icon":278},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F9.cli\u002F10.add-db","i-lucide-database-zap",{"title":280,"path":281,"stem":282,"icon":283},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F9.cli\u002F11.add-compose","i-lucide-container",{"title":285,"path":286,"stem":287,"icon":288},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F9.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":290,"path":291,"stem":292,"icon":293},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F9.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":295,"path":296,"stem":297,"icon":298},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F9.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":300,"path":301,"stem":302,"icon":165},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F9.cli\u002F15.add-seeder",{"title":304,"path":305,"stem":306,"icon":307},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F9.cli\u002F2.new-project","i-lucide-folder-plus",{"title":309,"path":310,"stem":311,"icon":312},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F9.cli\u002F3.new-module","i-lucide-blocks",{"title":314,"path":315,"stem":316,"icon":317},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F9.cli\u002F4.add-surface","i-lucide-layers-2",{"title":319,"path":320,"stem":321,"icon":322},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F9.cli\u002F5.add-core","i-lucide-box",{"title":324,"path":325,"stem":326,"icon":327},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F9.cli\u002F6.add-handler","i-lucide-webhook",{"title":329,"path":330,"stem":331,"icon":332},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F9.cli\u002F7.new-migration","i-lucide-file-plus",{"title":334,"path":335,"stem":336,"icon":195},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F9.cli\u002F8.worker-generators",{"title":338,"path":339,"stem":340,"icon":341},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F9.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"id":343,"title":21,"body":344,"description":1834,"extension":1835,"links":1836,"meta":1837,"navigation":1838,"path":22,"seo":1839,"stem":23,"__hash__":1840},"docs_en\u002Fen\u002F1.getting-started\u002F3.coming-from-symfony.md",{"type":345,"value":346,"toc":1827},"minimark",[347,356,368,373,815,819,822,1283,1286,1703,1722,1726,1765,1769,1801,1805,1823],[348,349,350,351,355],"p",{},"If you've worked with Symfony, gpsystem's way of thinking will feel familiar: the concepts behind Messenger, the EventDispatcher and Voters transfer one to one. This page shows what maps to what, and honestly, what does ",[352,353,354],"strong",{},"not"," transfer.",[348,357,358,359,363,364,367],{},"What carries over: message-based async processing, the event\u002Flistener way of thinking, and per-request authorization checks. What doesn't: there is no service container or autowiring (dependencies travel in an explicit ",[360,361,362],"code",{},"Dependencies"," struct that you fill in ",[360,365,366],{},"main.go","), no bundle system, and no Doctrine ORM: repositories contain SQL or the bun query builder.",[369,370,372],"h2",{"id":371},"the-mapping-table","The mapping table",[374,375,376,392],"table",{},[377,378,379],"thead",{},[380,381,382,386,389],"tr",{},[383,384,385],"th",{},"Symfony",[383,387,388],{},"gpsystem",[383,390,391],{},"Where",[393,394,395,424,446,470,488,512,527,542,560,577,592,610,633,649,665,680,695,713,730,742,763,783,799],"tbody",{},[380,396,397,405,418],{},[398,399,400,401,404],"td",{},"The Scheduler component (",[360,402,403],{},"#[AsPeriodicTask]",")",[398,406,407,410,411,414,415,404],{},[360,408,409],{},"scheduler.Schedule"," fluent API (",[360,412,413],{},"sched.DailyAt(...)",", ",[360,416,417],{},"sched.Job(...)",[398,419,420],{},[421,422,423],"a",{"href":188},"Scheduler",[380,425,426,432,442],{},[398,427,428,429,404],{},"Messenger (",[360,430,431],{},"#[AsMessageHandler]",[398,433,434,437,438,441],{},[360,435,436],{},"events.Event"," + ",[360,439,440],{},"events.Listen"," (every listener is an independent, retried task)",[398,443,444],{},[421,445,177],{"href":178},[380,447,448,454,464],{},[398,449,450,451,404],{},"Messenger transports, the worker (",[360,452,453],{},"messenger:consume",[398,455,456,459,460,463],{},[360,457,458],{},"queue"," (asynq + Redis) + the ",[360,461,462],{},"cmd\u002Fworker"," binary",[398,465,466,414,468],{},[421,467,173],{"href":174},[421,469,192],{"href":193},[380,471,472,478,484],{},[398,473,474,475,404],{},"Security Voters (",[360,476,477],{},"voteOnAttribute",[398,479,480,483],{},[360,481,482],{},"policy.Registry"," + generated enforcer middleware, fail-closed",[398,485,486],{},[421,487,213],{"href":214},[380,489,490,496,508],{},[398,491,492,493],{},"Role hierarchy, ",[360,494,495],{},"ROLE_*",[398,497,498,414,501,504,505],{},[360,499,500],{},"rbac.Identity",[360,502,503],{},"RequireRole"," \u002F ",[360,506,507],{},"RequirePermission",[398,509,510],{},[421,511,208],{"href":209},[380,513,514,517,523],{},[398,515,516],{},"The security firewall, authenticators",[398,518,519,522],{},[360,520,521],{},"auth"," (JWT issuing and middleware)",[398,524,525],{},[421,526,203],{"href":204},[380,528,529,532,538],{},[398,530,531],{},"MonologBundle (dev channel)",[398,533,534,537],{},[360,535,536],{},"logx\u002Fmonolog"," (a Monolog-format console handler)",[398,539,540],{},[421,541,227],{"href":228},[380,543,544,550,556],{},[398,545,546,547,404],{},"Doctrine Migrations (",[360,548,549],{},"doctrine:migrations:migrate",[398,551,552,555],{},[360,553,554],{},"go run .\u002Fcmd\u002Fmigrate"," (embedded, numbered SQL)",[398,557,558],{},[421,559,157],{"href":158},[380,561,562,567,573],{},[398,563,564],{},[360,565,566],{},"EntityManager::wrapInTransaction()",[398,568,569,572],{},[360,570,571],{},"Transactor.WithinTransaction(ctx, fn)"," (the transaction travels in the context)",[398,574,575],{},[421,576,152],{"href":153},[380,578,579,585,588],{},[398,580,581,582,404],{},"Validator attributes (",[360,583,584],{},"#[Assert\\NotBlank]",[398,586,587],{},"validator struct tags + generated request types",[398,589,590],{},[421,591,123],{"href":124},[380,593,594,600,606],{},[398,595,596,597],{},"Exception listeners, ",[360,598,599],{},"ExceptionEvent",[398,601,602,605],{},[360,603,604],{},"errs"," codes + public message → RFC 9457 problem+json \u002F Sentry",[398,607,608],{},[421,609,78],{"href":79},[380,611,612,620,629],{},[398,613,614,437,617],{},[360,615,616],{},"config\u002Fpackages\u002F*.yaml",[360,618,619],{},".env",[398,621,622,625,626,628],{},[360,623,624],{},"envconf.MustLoad[T]"," (a typed struct from env, with ",[360,627,619],{}," support)",[398,630,631],{},[421,632,52],{"href":83},[380,634,635,638,645],{},[398,636,637],{},"The Flysystem bundle (oneup)",[398,639,640,641,644],{},"the ",[360,642,643],{},"storage.Store"," interface + an S3 implementation",[398,646,647],{},[421,648,248],{"href":249},[380,650,651,654,660],{},[398,652,653],{},"The Mailer component",[398,655,640,656,659],{},[360,657,658],{},"mail.Message"," builder + MJML templates; the in-memory mailer is the test double",[398,661,662],{},[421,663,664],{"href":253},"Mail",[380,666,667,670,676],{},[398,668,669],{},"The Notifier component",[398,671,672,675],{},[360,673,674],{},"notify"," (multi-channel notifications)",[398,677,678],{},[421,679,257],{"href":258},[380,681,682,685,691],{},[398,683,684],{},"Mercure",[398,686,687,690],{},[360,688,689],{},"realtime"," (a first-party publish\u002Fsubscribe)",[398,692,693],{},[421,694,262],{"href":263},[380,696,697,703,708],{},[398,698,699,700,404],{},"MakerBundle (",[360,701,702],{},"make:*",[398,704,705],{},[360,706,707],{},"go tool gpsystem new\u002Fadd ...",[398,709,710],{},[421,711,712],{"href":272},"CLI",[380,714,715,718,726],{},[398,716,717],{},"Pagerfanta \u002F KnpPaginatorBundle",[398,719,720,504,723],{},[360,721,722],{},"paginate.Page[T]",[360,724,725],{},"paginate.CursorPage[T]",[398,727,728],{},[421,729,128],{"href":129},[380,731,732,735,738],{},[398,733,734],{},"Doctrine ORM",[398,736,737],{},"pgx (SQL) or bun (query builder), no ActiveRecord\u002FDoctrine-style simulation",[398,739,740],{},[421,741,133],{"href":140},[380,743,744,751,759],{},[398,745,746,747,750],{},"Route attributes (",[360,748,749],{},"#[Route]","), auto-discovery",[398,752,753,756,757,404],{},[360,754,755],{},"Register\u003CSurface>(router, deps)"," (an explicit call in ",[360,758,366],{},[398,760,761],{},[421,762,68],{"href":69},[380,764,765,768,779],{},[398,766,767],{},"A separate controller set per audience (public API vs admin behind a firewall)",[398,769,770,771,774,775,778],{},"a ",[352,772,773],{},"surface"," (per-audience bundle): ",[360,776,777],{},"surfaces\u002F\u003Csurface>\u002F",", with its own contract and middleware",[398,780,781],{},[421,782,314],{"href":315},[380,784,785,788,795],{},[398,786,787],{},"Entity methods + shared service logic",[398,789,790,791,794],{},"the module's ",[360,792,793],{},"core\u002F"," package (rules written once, called by every surface)",[398,796,797],{},[421,798,35],{"href":36},[380,800,801,804,811],{},[398,802,803],{},"Doctrine repositories",[398,805,806,807,810],{},"the module-level ",[360,808,809],{},"repository\u002F",": one per module, shared by every surface",[398,812,813],{},[421,814,35],{"href":36},[369,816,818],{"id":817},"two-examples-side-by-side","Two examples side by side",[348,820,821],{},"A Messenger handler: processing a domain message:",[823,824,825,1029],"code-group",{},[826,827,833],"pre",{"className":828,"code":829,"filename":830,"language":831,"meta":832,"style":832},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","#[AsMessageHandler]\nfinal class OrderPlacedHandler\n{\n    public function __invoke(OrderPlaced $message): void\n    {\n        $this->mailer->send(\n            (new TemplatedEmail())\n                ->to($message->email)\n                ->htmlTemplate('emails\u002Forder_confirmation.html.twig')\n                ->context(['orderId' => $message->orderId]),\n        );\n    }\n}\n","Symfony: src\u002FMessageHandler\u002FOrderPlacedHandler.php","php","",[360,834,835,844,858,865,897,903,921,936,958,979,1011,1017,1023],{"__ignoreMap":832},[836,837,840],"span",{"class":838,"line":839},"line",1,[836,841,843],{"class":842},"sTEyZ","#[AsMessageHandler]\n",[836,845,847,851,854],{"class":838,"line":846},2,[836,848,850],{"class":849},"spNyl","final",[836,852,853],{"class":849}," class",[836,855,857],{"class":856},"sBMFI"," OrderPlacedHandler\n",[836,859,861],{"class":838,"line":860},3,[836,862,864],{"class":863},"sMK4o","{\n",[836,866,868,871,874,878,881,884,887,890,893],{"class":838,"line":867},4,[836,869,870],{"class":849},"    public",[836,872,873],{"class":849}," function",[836,875,877],{"class":876},"s2Zo4"," __invoke",[836,879,880],{"class":863},"(",[836,882,883],{"class":856},"OrderPlaced",[836,885,886],{"class":863}," $",[836,888,889],{"class":842},"message",[836,891,892],{"class":863},"):",[836,894,896],{"class":895},"sbssI"," void\n",[836,898,900],{"class":838,"line":899},5,[836,901,902],{"class":863},"    {\n",[836,904,906,909,912,915,918],{"class":838,"line":905},6,[836,907,908],{"class":863},"        $this->",[836,910,911],{"class":842},"mailer",[836,913,914],{"class":863},"->",[836,916,917],{"class":876},"send",[836,919,920],{"class":863},"(\n",[836,922,924,927,930,933],{"class":838,"line":923},7,[836,925,926],{"class":863},"            (",[836,928,929],{"class":895},"new",[836,931,932],{"class":856}," TemplatedEmail",[836,934,935],{"class":863},"())\n",[836,937,939,942,945,948,950,952,955],{"class":838,"line":938},8,[836,940,941],{"class":863},"                ->",[836,943,944],{"class":876},"to",[836,946,947],{"class":863},"($",[836,949,889],{"class":842},[836,951,914],{"class":863},[836,953,954],{"class":842},"email",[836,956,957],{"class":863},")\n",[836,959,961,963,966,968,971,975,977],{"class":838,"line":960},9,[836,962,941],{"class":863},[836,964,965],{"class":876},"htmlTemplate",[836,967,880],{"class":863},[836,969,970],{"class":863},"'",[836,972,974],{"class":973},"sfazB","emails\u002Forder_confirmation.html.twig",[836,976,970],{"class":863},[836,978,957],{"class":863},[836,980,982,984,987,990,992,995,997,1000,1002,1004,1006,1008],{"class":838,"line":981},10,[836,983,941],{"class":863},[836,985,986],{"class":876},"context",[836,988,989],{"class":863},"([",[836,991,970],{"class":863},[836,993,994],{"class":973},"orderId",[836,996,970],{"class":863},[836,998,999],{"class":863}," =>",[836,1001,886],{"class":863},[836,1003,889],{"class":842},[836,1005,914],{"class":863},[836,1007,994],{"class":842},[836,1009,1010],{"class":863},"]),\n",[836,1012,1014],{"class":838,"line":1013},11,[836,1015,1016],{"class":863},"        );\n",[836,1018,1020],{"class":838,"line":1019},12,[836,1021,1022],{"class":863},"    }\n",[836,1024,1026],{"class":838,"line":1025},13,[836,1027,1028],{"class":863},"}\n",[826,1030,1035],{"className":1031,"code":1032,"filename":1033,"language":1034,"meta":832,"style":832},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","func sendOrderConfirmation(deps shop.Dependencies) func(context.Context, shopevents.OrderPlaced) error {\n    return func(ctx context.Context, ev shopevents.OrderPlaced) error {\n        msg := mail.NewMessage().\n            WithTo(ev.Email).\n            WithSubject(\"Your order confirmation\").\n            WithBody(mjml.Template(templates, \"templates\u002Forder_confirmation.mjml.tmpl\",\n                map[string]any{\"OrderID\": ev.OrderID}))\n        return deps.Mailer.Send(ctx, msg)\n    }\n}\n","gpsystem: internal\u002Fmodules\u002Fshop\u002Flisteners\u002Fsend_order_confirmation.go","go",[360,1036,1037,1089,1124,1143,1160,1177,1210,1246,1275,1279],{"__ignoreMap":832},[836,1038,1039,1042,1045,1047,1051,1054,1057,1059,1061,1064,1066,1068,1071,1074,1077,1079,1081,1083,1086],{"class":838,"line":839},[836,1040,1041],{"class":863},"func",[836,1043,1044],{"class":876}," sendOrderConfirmation",[836,1046,880],{"class":863},[836,1048,1050],{"class":1049},"sHdIc","deps",[836,1052,1053],{"class":856}," shop",[836,1055,1056],{"class":863},".",[836,1058,362],{"class":856},[836,1060,404],{"class":863},[836,1062,1063],{"class":863}," func(",[836,1065,986],{"class":856},[836,1067,1056],{"class":863},[836,1069,1070],{"class":856},"Context",[836,1072,1073],{"class":863},",",[836,1075,1076],{"class":856}," shopevents",[836,1078,1056],{"class":863},[836,1080,883],{"class":856},[836,1082,404],{"class":863},[836,1084,1085],{"class":849}," error",[836,1087,1088],{"class":863}," {\n",[836,1090,1091,1095,1097,1100,1103,1105,1107,1109,1112,1114,1116,1118,1120,1122],{"class":838,"line":846},[836,1092,1094],{"class":1093},"s7zQu","    return",[836,1096,1063],{"class":863},[836,1098,1099],{"class":1049},"ctx",[836,1101,1102],{"class":856}," context",[836,1104,1056],{"class":863},[836,1106,1070],{"class":856},[836,1108,1073],{"class":863},[836,1110,1111],{"class":1049}," ev",[836,1113,1076],{"class":856},[836,1115,1056],{"class":863},[836,1117,883],{"class":856},[836,1119,404],{"class":863},[836,1121,1085],{"class":849},[836,1123,1088],{"class":863},[836,1125,1126,1129,1132,1135,1137,1140],{"class":838,"line":860},[836,1127,1128],{"class":842},"        msg ",[836,1130,1131],{"class":863},":=",[836,1133,1134],{"class":842}," mail",[836,1136,1056],{"class":863},[836,1138,1139],{"class":876},"NewMessage",[836,1141,1142],{"class":863},"().\n",[836,1144,1145,1148,1150,1153,1155,1157],{"class":838,"line":867},[836,1146,1147],{"class":876},"            WithTo",[836,1149,880],{"class":863},[836,1151,1152],{"class":842},"ev",[836,1154,1056],{"class":863},[836,1156,252],{"class":842},[836,1158,1159],{"class":863},").\n",[836,1161,1162,1165,1167,1170,1173,1175],{"class":838,"line":899},[836,1163,1164],{"class":876},"            WithSubject",[836,1166,880],{"class":863},[836,1168,1169],{"class":863},"\"",[836,1171,1172],{"class":973},"Your order confirmation",[836,1174,1169],{"class":863},[836,1176,1159],{"class":863},[836,1178,1179,1182,1184,1187,1189,1192,1194,1197,1199,1202,1205,1207],{"class":838,"line":905},[836,1180,1181],{"class":876},"            WithBody",[836,1183,880],{"class":863},[836,1185,1186],{"class":842},"mjml",[836,1188,1056],{"class":863},[836,1190,1191],{"class":876},"Template",[836,1193,880],{"class":863},[836,1195,1196],{"class":842},"templates",[836,1198,1073],{"class":863},[836,1200,1201],{"class":863}," \"",[836,1203,1204],{"class":973},"templates\u002Forder_confirmation.mjml.tmpl",[836,1206,1169],{"class":863},[836,1208,1209],{"class":863},",\n",[836,1211,1212,1215,1218,1221,1224,1227,1229,1232,1234,1237,1239,1241,1243],{"class":838,"line":923},[836,1213,1214],{"class":863},"                map[",[836,1216,1217],{"class":849},"string",[836,1219,1220],{"class":863},"]",[836,1222,1223],{"class":856},"any",[836,1225,1226],{"class":863},"{",[836,1228,1169],{"class":863},[836,1230,1231],{"class":973},"OrderID",[836,1233,1169],{"class":863},[836,1235,1236],{"class":863},":",[836,1238,1111],{"class":842},[836,1240,1056],{"class":863},[836,1242,1231],{"class":842},[836,1244,1245],{"class":863},"}))\n",[836,1247,1248,1251,1254,1256,1259,1261,1264,1266,1268,1270,1273],{"class":838,"line":938},[836,1249,1250],{"class":1093},"        return",[836,1252,1253],{"class":842}," deps",[836,1255,1056],{"class":863},[836,1257,1258],{"class":842},"Mailer",[836,1260,1056],{"class":863},[836,1262,1263],{"class":876},"Send",[836,1265,880],{"class":863},[836,1267,1099],{"class":842},[836,1269,1073],{"class":863},[836,1271,1272],{"class":842}," msg",[836,1274,957],{"class":863},[836,1276,1277],{"class":838,"line":960},[836,1278,1022],{"class":863},[836,1280,1281],{"class":838,"line":981},[836,1282,1028],{"class":863},[348,1284,1285],{},"An authorization check: per-request ownership checks for when a role alone isn't enough:",[823,1287,1288,1457],{},[826,1289,1292],{"className":828,"code":1290,"filename":1291,"language":831,"meta":832,"style":832},"class OrderVoter extends Voter\n{\n    protected function voteOnAttribute(string $attribute, mixed $order, TokenInterface $token): bool\n    {\n        $user = $token->getUser();\n        return $order->getUserId() === $user->getId()\n            || in_array('ROLE_ADMIN', $user->getRoles(), true);\n    }\n}\n","Symfony: src\u002FSecurity\u002FVoter\u002FOrderVoter.php",[360,1293,1294,1308,1312,1356,1360,1383,1415,1449,1453],{"__ignoreMap":832},[836,1295,1296,1299,1302,1305],{"class":838,"line":839},[836,1297,1298],{"class":849},"class",[836,1300,1301],{"class":856}," OrderVoter",[836,1303,1304],{"class":849}," extends",[836,1306,1307],{"class":856}," Voter\n",[836,1309,1310],{"class":838,"line":846},[836,1311,864],{"class":863},[836,1313,1314,1317,1319,1322,1324,1326,1328,1331,1333,1336,1338,1341,1343,1346,1348,1351,1353],{"class":838,"line":860},[836,1315,1316],{"class":849},"    protected",[836,1318,873],{"class":849},[836,1320,1321],{"class":876}," voteOnAttribute",[836,1323,880],{"class":863},[836,1325,1217],{"class":895},[836,1327,886],{"class":863},[836,1329,1330],{"class":842},"attribute",[836,1332,1073],{"class":863},[836,1334,1335],{"class":895}," mixed",[836,1337,886],{"class":863},[836,1339,1340],{"class":842},"order",[836,1342,1073],{"class":863},[836,1344,1345],{"class":856}," TokenInterface",[836,1347,886],{"class":863},[836,1349,1350],{"class":842},"token",[836,1352,892],{"class":863},[836,1354,1355],{"class":895}," bool\n",[836,1357,1358],{"class":838,"line":867},[836,1359,902],{"class":863},[836,1361,1362,1365,1368,1371,1373,1375,1377,1380],{"class":838,"line":899},[836,1363,1364],{"class":863},"        $",[836,1366,1367],{"class":842},"user ",[836,1369,1370],{"class":863},"=",[836,1372,886],{"class":863},[836,1374,1350],{"class":842},[836,1376,914],{"class":863},[836,1378,1379],{"class":876},"getUser",[836,1381,1382],{"class":863},"();\n",[836,1384,1385,1387,1389,1391,1393,1396,1399,1402,1404,1407,1409,1412],{"class":838,"line":905},[836,1386,1250],{"class":1093},[836,1388,886],{"class":863},[836,1390,1340],{"class":842},[836,1392,914],{"class":863},[836,1394,1395],{"class":876},"getUserId",[836,1397,1398],{"class":863},"()",[836,1400,1401],{"class":863}," ===",[836,1403,886],{"class":863},[836,1405,1406],{"class":842},"user",[836,1408,914],{"class":863},[836,1410,1411],{"class":876},"getId",[836,1413,1414],{"class":863},"()\n",[836,1416,1417,1420,1423,1425,1427,1430,1432,1434,1436,1438,1440,1443,1446],{"class":838,"line":923},[836,1418,1419],{"class":863},"            ||",[836,1421,1422],{"class":876}," in_array",[836,1424,880],{"class":863},[836,1426,970],{"class":863},[836,1428,1429],{"class":973},"ROLE_ADMIN",[836,1431,970],{"class":863},[836,1433,1073],{"class":863},[836,1435,886],{"class":863},[836,1437,1406],{"class":842},[836,1439,914],{"class":863},[836,1441,1442],{"class":876},"getRoles",[836,1444,1445],{"class":863},"(),",[836,1447,1448],{"class":863}," true);\n",[836,1450,1451],{"class":838,"line":938},[836,1452,1022],{"class":863},[836,1454,1455],{"class":838,"line":960},[836,1456,1028],{"class":863},[826,1458,1461],{"className":1031,"code":1459,"filename":1460,"language":1034,"meta":832,"style":832},"func New() *kitpolicy.Registry {\n    reg := kitpolicy.NewRegistry()\n    reg.Register(\"orders.view\", func(ctx context.Context, id *rbac.Identity, req any) error {\n        if id.HasRole(\"admin\") {\n            return nil\n        }\n        r := req.(gen.GetOrderRequest)\n        if !ownsOrder(ctx, id, r.OrderId) {\n            return kitpolicy.Deny(\"You can only view your own orders.\")\n        }\n        return nil\n    })\n    return reg\n}\n","gpsystem: internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fpolicy\u002Fpolicy.go",[360,1462,1463,1485,1502,1562,1587,1595,1600,1622,1654,1676,1680,1686,1691,1698],{"__ignoreMap":832},[836,1464,1465,1467,1470,1472,1475,1478,1480,1483],{"class":838,"line":839},[836,1466,1041],{"class":863},[836,1468,1469],{"class":876}," New",[836,1471,1398],{"class":863},[836,1473,1474],{"class":863}," *",[836,1476,1477],{"class":856},"kitpolicy",[836,1479,1056],{"class":863},[836,1481,1482],{"class":856},"Registry",[836,1484,1088],{"class":863},[836,1486,1487,1490,1492,1495,1497,1500],{"class":838,"line":846},[836,1488,1489],{"class":842},"    reg ",[836,1491,1131],{"class":863},[836,1493,1494],{"class":842}," kitpolicy",[836,1496,1056],{"class":863},[836,1498,1499],{"class":876},"NewRegistry",[836,1501,1414],{"class":863},[836,1503,1504,1507,1509,1512,1514,1516,1519,1521,1523,1525,1527,1529,1531,1533,1535,1538,1540,1543,1545,1548,1550,1553,1556,1558,1560],{"class":838,"line":860},[836,1505,1506],{"class":842},"    reg",[836,1508,1056],{"class":863},[836,1510,1511],{"class":876},"Register",[836,1513,880],{"class":863},[836,1515,1169],{"class":863},[836,1517,1518],{"class":973},"orders.view",[836,1520,1169],{"class":863},[836,1522,1073],{"class":863},[836,1524,1063],{"class":863},[836,1526,1099],{"class":1049},[836,1528,1102],{"class":856},[836,1530,1056],{"class":863},[836,1532,1070],{"class":856},[836,1534,1073],{"class":863},[836,1536,1537],{"class":1049}," id",[836,1539,1474],{"class":863},[836,1541,1542],{"class":856},"rbac",[836,1544,1056],{"class":863},[836,1546,1547],{"class":856},"Identity",[836,1549,1073],{"class":863},[836,1551,1552],{"class":1049}," req",[836,1554,1555],{"class":856}," any",[836,1557,404],{"class":863},[836,1559,1085],{"class":849},[836,1561,1088],{"class":863},[836,1563,1564,1567,1569,1571,1574,1576,1578,1581,1583,1585],{"class":838,"line":867},[836,1565,1566],{"class":1093},"        if",[836,1568,1537],{"class":842},[836,1570,1056],{"class":863},[836,1572,1573],{"class":876},"HasRole",[836,1575,880],{"class":863},[836,1577,1169],{"class":863},[836,1579,1580],{"class":973},"admin",[836,1582,1169],{"class":863},[836,1584,404],{"class":863},[836,1586,1088],{"class":863},[836,1588,1589,1592],{"class":838,"line":899},[836,1590,1591],{"class":1093},"            return",[836,1593,1594],{"class":863}," nil\n",[836,1596,1597],{"class":838,"line":905},[836,1598,1599],{"class":863},"        }\n",[836,1601,1602,1605,1607,1609,1612,1615,1617,1620],{"class":838,"line":923},[836,1603,1604],{"class":842},"        r ",[836,1606,1131],{"class":863},[836,1608,1552],{"class":842},[836,1610,1611],{"class":863},".(",[836,1613,1614],{"class":856},"gen",[836,1616,1056],{"class":863},[836,1618,1619],{"class":856},"GetOrderRequest",[836,1621,957],{"class":863},[836,1623,1624,1626,1629,1632,1634,1636,1638,1640,1642,1645,1647,1650,1652],{"class":838,"line":938},[836,1625,1566],{"class":1093},[836,1627,1628],{"class":863}," !",[836,1630,1631],{"class":876},"ownsOrder",[836,1633,880],{"class":863},[836,1635,1099],{"class":842},[836,1637,1073],{"class":863},[836,1639,1537],{"class":842},[836,1641,1073],{"class":863},[836,1643,1644],{"class":842}," r",[836,1646,1056],{"class":863},[836,1648,1649],{"class":842},"OrderId",[836,1651,404],{"class":863},[836,1653,1088],{"class":863},[836,1655,1656,1658,1660,1662,1665,1667,1669,1672,1674],{"class":838,"line":960},[836,1657,1591],{"class":1093},[836,1659,1494],{"class":842},[836,1661,1056],{"class":863},[836,1663,1664],{"class":876},"Deny",[836,1666,880],{"class":863},[836,1668,1169],{"class":863},[836,1670,1671],{"class":973},"You can only view your own orders.",[836,1673,1169],{"class":863},[836,1675,957],{"class":863},[836,1677,1678],{"class":838,"line":981},[836,1679,1599],{"class":863},[836,1681,1682,1684],{"class":838,"line":1013},[836,1683,1250],{"class":1093},[836,1685,1594],{"class":863},[836,1687,1688],{"class":838,"line":1019},[836,1689,1690],{"class":863},"    })\n",[836,1692,1693,1695],{"class":838,"line":1025},[836,1694,1094],{"class":1093},[836,1696,1697],{"class":842}," reg\n",[836,1699,1701],{"class":838,"line":1700},14,[836,1702,1028],{"class":863},[348,1704,1705,1706,1709,1710,1713,1714,1717,1718,1721],{},"One substantive difference: a Symfony Voter can also respond with ",[360,1707,1708],{},"abstain"," (declining to decide, deferring to the rest of the chain), and the ",[360,1711,1712],{},"denyAccessUnlessGranted()"," call in the controller can be forgotten. Here the policy is attached to the operation by the ",[360,1715,1716],{},"@policy"," decorator in the TypeSpec contract, and the enforcer middleware enforces it from the generated table. An operation that is tagged but has no registered policy doesn't slip through, it errors (",[421,1719,1720],{"href":214},"fail-closed","); there is no \"abstain\" state.",[369,1723,1725],{"id":1724},"behavioral-differences","Behavioral differences",[1727,1728,1729,1742,1753],"ul",{},[1730,1731,1732,1735,1736,1739,1740,1056],"li",{},[352,1733,1734],{},"Events and fan-out."," Symfony Messenger routes one message to one handler (multiple handlers can be registered, but typically one retry configuration applies to the message). gpsystem treats every listener as an ",[352,1737,1738],{},"independent asynq task with its own retry budget",": one event fans out into N listener tasks, and a failing listener's retry never re-runs the others. Details: ",[421,1741,177],{"href":178},[1730,1743,1744,1747,1748,1750,1751,1056],{},[352,1745,1746],{},"Transactional outbox."," Doctrine's ",[360,1749,566],{}," handles the pre-commit rollback case, but doesn't close the post-commit crash window (the process dying between the DB commit and the message dispatch). gpsystem's outbox writes the event into the same database, in the same transaction, as the business change, by default. Details: ",[421,1752,182],{"href":183},[1730,1754,1755,1758,1759,1762,1763,1056],{},[352,1756,1757],{},"Migrations."," Doctrine Migrations has a schema-diff generator (",[360,1760,1761],{},"doctrine:migrations:diff","); gpsystem doesn't, only plain, hand-written SQL, a deliberate trade-off against the diff generator's tendency to hide the actual DDL. Details: ",[421,1764,157],{"href":158},[369,1766,1768],{"id":1767},"whats-missing-and-why","What's missing, and why",[1727,1770,1771,1786,1795],{},[1730,1772,1773,1776,1777,1779,1780,1782,1783,1056],{},[352,1774,1775],{},"No service container or autowiring."," The ",[360,1778,362],{}," struct is built by hand in ",[360,1781,366],{},". In exchange, the answer to \"where does this dependency come from?\" is always a line in ",[360,1784,1785],{},"cmd\u002F\u003Cmodule>\u002Fmain.go",[1730,1787,1788,1791,1792,1794],{},[352,1789,1790],{},"No Doctrine ORM."," The repository layer works with pgx (SQL) or bun (query builder). Explicit queries are the established path in the Go ecosystem. ",[421,1793,148],{"href":149}," is an optional adapter if you want query-builder comfort.",[1730,1796,1797,1800],{},[352,1798,1799],{},"No Twig."," The kit focuses on API backends; server-side rendering is not part of it.",[369,1802,1804],{"id":1803},"background","Background",[348,1806,1807,1808,1811,1812,1815,1816,1822],{},"The kit author's motivation was precisely to have a system in the Go ecosystem that is as easy to pick up as the batteries-included PHP frameworks: one where on day one of a project you write business logic, not evaluate infrastructure. The Go world's strength lies in small, independent, well-tested libraries: gpsystem doesn't want to replace them, but to offer a single, reconciled and maintained composition of them. The library half is a ",[352,1809,1810],{},"microservice chassis",", the generated project a ",[352,1813,1814],{},"service template"," (",[421,1817,1821],{"href":1818,"rel":1819},"https:\u002F\u002Fmicroservices.io\u002Fpatterns\u002Fmicroservice-chassis.html",[1820],"nofollow","Richardson","), not a runtime layer living above your app. The goal is a comfortable start, not a new, closed framework.",[1824,1825,1826],"style",{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}",{"title":832,"searchDepth":846,"depth":846,"links":1828},[1829,1830,1831,1832,1833],{"id":371,"depth":846,"text":372},{"id":817,"depth":846,"text":818},{"id":1724,"depth":846,"text":1725},{"id":1767,"depth":846,"text":1768},{"id":1803,"depth":846,"text":1804},"A concept-by-concept mapping between Symfony and gpsystem.","md",null,{},{"icon":19},{"title":21,"description":1834},"g7JJpzGnPxTG10XgjxCBsR3Pp6YBTHL-6awXIsMCA7g",[1842,1844],{"title":16,"path":17,"stem":18,"description":1843,"icon":19,"children":-1},"A concept-by-concept mapping between Laravel and gpsystem.",{"title":25,"path":26,"stem":27,"description":1845,"icon":28,"children":-1},"Prerequisites, accessing the private kit, and creating your first gpsystem project.",1784668701128]