Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laragento Laravel Package

genaker/laragento

Laragento bridges Magento and Laravel, letting you query Magento products, orders, and customers through familiar Eloquent-style models. Connect to your store, build integrations, sync catalogs, and extend Magento with Laravel's tooling and conventions—no boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish config

composer require genaker/laragento
php artisan vendor:publish --provider="Genaker\Laragento\LaragentoServiceProvider"

The published config/laragento.php holds the Magento DB credentials. Treat that file as the source of truth for the env key names — the package was last released in Feb 2022, so verify the keys against the file rather than against a blog post.

2. Point it at a read replica, and create a SELECT-only MySQL user

This is the single most important setup step. Do not hand the package your Magento app user.

CREATE USER 'laravel_ro'@'%' IDENTIFIED BY '...';
GRANT SELECT, SHOW VIEW ON magento.* TO 'laravel_ro'@'%';

Magento stores its table prefix (db_prefix in app/etc/env.php) separately from the connection — set it on the connection config or every query will hit sales_order when the table is actually mage_sales_order.

3. Prefer an explicit Laravel connection over the package's globals

Even if the provider registers its own connection, defining a named one keeps you in control and survives package abandonment:

// config/database.php
'magento' => [
    'driver'    => 'mysql',
    'host'      => env('MAGENTO_DB_HOST'),
    'database'  => env('MAGENTO_DB_DATABASE'),
    'username'  => env('MAGENTO_DB_USERNAME'),
    'password'  => env('MAGENTO_DB_PASSWORD'),
    'prefix'    => env('MAGENTO_DB_PREFIX', ''),
    'charset'   => 'utf8',
    'collation' => 'utf8_general_ci',
    'strict'    => false,   // Magento's own queries assume permissive mode
],

4. Where to look first

  • src/Models/ — the shipped Eloquent models (sales orders, products, customers). Read them before writing your own; they already set $primaryKey = 'entity_id', $connection, and often $timestamps = false.
  • config/laragento.php — connection shape.
  • The service provider — it tells you whether a connection named laragento is auto-registered, and whether the models hardcode that name.

5. First use case: an order-lookup / reporting screen

The highest-value, lowest-risk entry point is read-only reporting — something Magento's admin is bad at and your Laravel app is good at.

use Illuminate\Support\Facades\DB;

$orders = DB::connection('magento')
    ->table('sales_order_grid')
    ->whereBetween('created_at', [$from, $to])
    ->where('store_id', $storeId)
    ->orderByDesc('entity_id')
    ->paginate(50);

sales_order_grid and customer_grid_flat are pre-joined, pre-computed, and indexed — they are almost always the right table for list views.

Implementation Patterns

Model per Magento aggregate, never per table. Magento's schema is a join graph; a model is a read-side aggregate. Keep them in App\Magento\Models with an Magento base class that centralises connection and key conventions:

abstract class MagentoModel extends Model
{
    protected $connection = 'magento';
    protected $primaryKey = 'entity_id';
    public $timestamps = false;
    public $guarded = ['*'];   // read-only by accident, too
}

class SalesOrder extends MagentoModel
{
    protected $table = 'sales_order';

    public function items()
    {
        return $this->hasMany(SalesOrderItem::class, 'order_id', 'entity_id');
    }

    public function addresses()
    {
        return $this->hasMany(SalesOrderAddress::class, 'parent_id', 'entity_id');
    }
}

Two-tier reads: grid table for lists, entity tables for detail. sales_order_grid for the index page; sales_order + sales_order_item + sales_order_address for the detail page. Same for customers (customer_grid_flat vs customer_entity + customer_address_entity).

Resolve EAV once, cache forever. Never join eav_attribute in a hot path. Resolve attribute IDs at boot into the Laravel cache and reuse:

$nameAttrId = Cache::rememberForever('magento.attr.product.name', fn () =>
    DB::connection('magento')->table('eav_attribute')
        ->where('entity_type_id', 4)          // catalog_product
        ->where('attribute_code', 'name')
        ->value('attribute_id')
);

Then join only the one value table you need (catalog_product_entity_varchar for name, _decimal for price, _int for status/visibility), with store_id = 0 for the default-scope value and a COALESCE against the store-specific row.

If flat catalog is enabled, use it. catalog_product_flat_{storeId} is a single denormalised table with all attributes — it turns a five-join EAV query into a WHERE. Check core_config_data (catalog/frontend/flat_catalog_product) before relying on it.

Chunk everything that can grow. sales_order on a busy store is millions of rows. chunkById(1000), lazy(), cursor() — never ->get() on an unbounded order query. For exports, cursor() + streamed response.

Use query objects / repositories, not controllers. One class per report keeps the Magento SQL in one place when (not if) you need to swap tables:

class OrderRevenueReport
{
    public function __construct(private Connection $db) {}

    public function daily(string $from, string $to): Collection
    {
        return $this->db->table('sales_order')
            ->selectRaw('DATE(created_at) d, store_id, SUM(base_grand_total) total, COUNT(*) orders')
            ->where('state', 'complete')
            ->whereBetween('created_at', [$from, $to])
            ->groupBy('d', 'store_id')
            ->get();
    }
}

Cache the small, hot dimension tables. store, store_website, store_group, eav_attribute_set — a few hundred rows at most. Load once into a repository, resolve store_id → code in PHP.

ETL into your own schema for anything dashboard-heavy. Don't run eight aggregations against the live Magento box on every page load. An Artisan command that upserts into a reporting_orders table on a schedule is the pattern that survives production.

Integration tips

  • php artisan tinker with DB::connection('magento')->table(...) is the fastest way to explore the schema — faster than reading db_schema.xml.
  • Disable query logging (DB::connection('magento')->disableQueryLog()) in any loop; the debug bar will OOM on a 500k-row chunk job.
  • If you expose this via API, wrap in Laravel API Resources with explicit select() — Magento tables have 80+ columns each and serialising sales_order raw leaks internal fields.

Gotchas and Tips

License: GPL-3.0. This is the first thing to check with legal. GPL is copyleft; linking it into a proprietary Laravel codebase is a distribution question most companies won't want to answer. Many teams use it strictly as a dev/reference aid and reimplement the (trivial) connection + base-model plumbing themselves — which is genuinely about 40 lines.

It is a read tool. Do not write. Eloquent save() against Magento tables silently corrupts the store: indexers don't run, sequence_* tables aren't advanced (auto-increment is emulated), EAV index tables go stale, and caches aren't invalidated. Enforce it at the DB grant level (SELECT only) — that's the only guardrail that actually holds.

Stale package. Last release Feb 2022, so it targets Laravel 8/9-era APIs. Pin it and be prepared to fork. The models are thin enough that replacing them with your own subclasses of Illuminate\Database\Eloquent\Model is a low-effort hedge.

Primary keys are entity_id, not id. find(), route-model binding, and whereKey() all break if you forget $primaryKey. Same for item_id, value_id, attribute_id on the sub-tables.

Timestamps are UTC, and Eloquent's conventions don't hold. Magento writes created_at/updated_at in UTC; display conversion is a presentation concern. Set $timestamps = false and $dateFormat explicitly. Many tables have no updated_at at all.

Encrypted columns are unreadable. Magento encrypts at rest using the crypt/key in app/etc/env.php (AES-256-CBC, sometimes with a key ring in 2.4). Reading customer_entity.rp_token, encrypted config in core_config_data, or certain payment fields gives you ciphertext. You cannot decrypt without that key — and you probably shouldn't pull it into the Laravel app.

Password hashes are not Laravel hashes. customer_entity.password_hash is hash:salt:version with Argon2id in 2.4. Hash::check() will fail. If you need auth against Magento customers, port Magento's Encryptor verification.

Serialized PHP everywhere. sales_order_item.product_options, additional_data, quote_item blobs are serialize() output, not JSON. Use unserialize($v, ['allowed_classes' => false]) — and be aware older data may be PHP-serialized while newer is JSON.

Money columns are ambiguous. grand_total is in order currency, base_grand_total in base currency. For cross-store reporting always aggregate on base_*, or your totals are nonsense.

Grid tables can be stale. sales_order_grid is populated by an indexer. If Magento's cron is broken, your "live" dashboard is quietly wrong. For anything that must be exact, hit sales_order directly.

Store scoping. store_id = 0 is admin/default scope. A global-scope attribute value lives at store_id = 0 and is overridden per store. Missing the COALESCE(default, store) pattern is the classic bug that makes product names disappear on non-default store views.

Commerce vs Open Source. Magento Commerce with content staging adds row_id/link_field to product entities, so the EAV join key differs (row_id instead of entity_id). Any EAV query you write is version- and edition-specific — wrap it and test against the actual instance.

Config caching bites. php artisan config:cache freezes the Magento credentials; changing .env without re-caching silently keeps the old connection. Also watch ONLY_FULL_GROUP_BY — Magento's own my.cnf typically relaxes it, your Laravel connection should match ('strict' => false).

Extension points worth knowing: the connection name (so you can point at a replica per environment), the base model (so you can add global scopes for store_id filtering), and the config file (so you can swap credentials via a secrets manager). If the package doesn't expose these cleanly, that's your first fork.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons