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

Partner Laravel Package

mybizna/partner

mybizna/partner is a Laravel package for managing business partners—customers, suppliers, and contacts—within the MyBizna ecosystem. It provides models, migrations, and CRUD scaffolding to store partner records, link them to your app, and reuse them across modules.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install with an exact pin, not a range. The package's version tags sort counter-intuitively under Composer normalization (2025.001 → 2025.1.0, which is below 2025.2 → 2025.2.0). Don't write ^2025.2 — you may silently resolve to the older line, or fail to resolve at all.

composer require mybizna/partner:2025.001
git add composer.lock

2. Resolve the unknowns in composer.json before writing any code. The release note is a bare compare link, so the dependency contract is the documentation:

cat vendor/mybizna/partner/composer.json

Check specifically: the illuminate/support constraint (which Laravel majors), the PHP floor, and whether it requires a sibling package (mybizna/core or similar). If it hard-requires a core package, install that first and expect it to bring user/tenant modeling assumptions with it.

3. Publish config and migrations, then read them.

php artisan vendor:publish --provider="MyBizna\Partner\PartnerServiceProvider"
php artisan migrate
php artisan migrate:rollback   # confirm reversibility immediately

Open the published config/partner.php first — it is the highest-signal file in the package. The description advertises "configurable partner types and statuses"; those keys tell you the intended domain vocabulary (customer / supplier / contact, and the lifecycle states) before you read a single model.

4. Read the models before the controllers. Expect something shaped like:

src/
  Models/Partner.php
  Models/Contact.php
  Models/Address.php
  Providers/PartnerServiceProvider.php
  config/partner.php
  database/migrations/

Map the relationships by hand — Partner hasMany Contact, Partner hasMany Address, and a morphTo/morphMany pair used to attach partners to arbitrary entities. That polymorphic join is the package's actual value proposition; everything else is CRUD scaffolding you could write yourself in an afternoon.

5. First use case: register a counterparty and attach it to your own domain object.

use MyBizna\Partner\Models\Partner; // verify namespace from composer.json autoload

$partner = Partner::create([
    'name'  => 'Acme Industrial Ltd',
    'type'  => config('partner.types.customer'), // don't hardcode the string
    'status'=> config('partner.statuses.active'),
]);

$partner->contacts()->create([
    'name'  => 'Jane Doe',
    'email' => '[email protected]',
]);

$partner->addresses()->create([
    'line_1' => '1 Example Way',
    'city'   => 'Leeds',
]);

// Attach to your own entity via the polymorphic side
$invoice->partner()->associate($partner)->save();
// — or, if the morph is on Partner's side:
$partner->partners()->attach($invoice); // verify method names on Partner

The exact method names are guessable but not guaranteed. Run php artisan tinker and inspect the model once — Partner::first() plus get_class_methods() costs thirty seconds and saves an hour of guessing.

First milestone to aim for: a partner registry screen (list + create + view contacts) behind a feature flag, in a disposable database. Do not point this at real data until you have read the migration diff.

Implementation Patterns

Treat Partner as an aggregate root, not a table row. The package deliberately bundles partner + contacts + addresses. Day-to-day, that means most writes are multi-model: create the partner, then its primary contact, then its billing address, ideally inside one transaction.

DB::transaction(function () use ($payload) {
    $partner = Partner::create($payload->partnerAttributes());

    $partner->contacts()->createMany($payload->contacts());
    $partner->addresses()->createMany($payload->addresses());

    return $partner;
});

Wrap this in a single action class (RegisterPartner, SyncPartnerFromErp) rather than calling Partner::create() from three different controllers. The package ships models; the service boundary is yours to own, and it is the thing that will actually pay back over time.

Consume the config as the source of truth for types and statuses. If config/partner.php defines types and statuses, build your form selects, validation rules, and display labels from those keys — never from literal strings.

// Form request
public function rules(): array
{
    return [
        'type'   => ['required', Rule::in(array_keys(config('partner.types', [])))],
        'status' => ['required', Rule::in(array_keys(config('partner.statuses', [])))],
    ];
}

// Blade
<x-select :options="config('partner.types')" name="type" />

This is the cheapest extension point the package gives you: adding a new partner type is a config change, not a migration. If the package ships a config key → model/table mapping (some MyBizNa modules do), use that rather than extending the class directly, because it survives package upgrades.

Use the polymorphic relation as your integration seam, and normalize it. Every place your app says "this record belongs to a counterparty" should go through the package's morph rather than a bespoke partner_id column you add yourself. The point of adopting the package is to have one way to answer "who is this counterparty?" across invoices, orders, projects, and tickets.

If you need to query from your side, declare the inverse on your own models:

class Invoice extends Model
{
    public function partner(): MorphTo
    {
        return $this->morphTo();
    }
}

Eager-load relationships in every list view. Partners fan out to contacts and addresses; a registry table that renders primary contact per row will N+1 instantly.

$partners = Partner::query()
    ->with(['contacts', 'addresses'])
    ->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
    ->paginate(25);

Prefer a constrained eager load if you only render the primary contact: ->with(['contacts' => fn ($q) => $q->limit(1)]). Confirm the package doesn't already define a primaryContact accessor — check the model before duplicating derived state.

Authorize at the policy layer, not in controllers. Register a policy for the package's model in your own AuthServiceProvider. Because the model lives in vendor/, you cannot add #[ScopedBy] or tenancy traits to it without extending it — so policies plus global scopes registered from your app are the practical tools.

Gate::policy(\MyBizna\Partner\Models\Partner::class, PartnerPolicy::class);

Check first whether the package already registers a policy or a tenant global scope. Double-registering produces confusing "allowed for everyone" behavior.

Build factories locally for tests. A zero-dependency package will not ship the factories your test suite wants. Add database/factories/PartnerFactory.php in your app that targets the vendor model — it keeps test setup stable even if the package later ships its own.

Seed reference data from config, not from fixtures. Types and statuses are config-driven, so a seeder that inserts them into a table (if such a table exists) will drift the moment someone edits the config. If the package stores types in a table, seed from config('partner.types') so the two can never diverge.

Pattern for the common "we already have vendors" case: do not migrate your existing vendors table row-for-row into the package on day one. Write a read-only bridge (an adapter that maps your vendor record to a partner-shaped DTO) behind the same interface, ship the package for new counterparties only, and reconcile later. The data-model decision is the expensive irreversible one; defer it until you have read the schema in anger.

Gotchas and Tips

The version tags are ordered backwards. 2025.001 normalizes below 2025.2. Practical consequences: composer update may not move you forward, ^2025.2 excludes the "latest" release, and Packagist's version list is ambiguous. Pin exactly, commit the lockfile, and treat any future composer require as a deliberate act. If you ever need to hold a patch, fork rather than relying on a constraint — a range constraint against this tag scheme is meaningless.

The changelog is a diff. The 2025.001 release note is a GitHub compare link and nothing else. Budget half a day per upgrade to read it. Triage into: migration edits, public method signature changes, config key renames, dependency bumps. If migrations were edited in place rather than added, your existing environment and a fresh environment will silently diverge — checksum vendor/mybizna/partner/database/migrations/*.php before and after every upgrade and treat any change to an already-run file as an incident.

Config caching will bite you if you read config dynamically. In production, config:cache freezes config/partner.php. If you build a type list from config('partner.types') inside a loop that also calls config() with a variable key (config("partner.types.{$type}")), all is well — but if the package itself calls config() at boot time after you've cached, or if you add new types to the config file and forget php artisan config:clear, you'll debug a phantom. Make config:clear part of the deploy script, and never assume a config edit takes effect without it.

Deleting a partner is the dangerous operation. Polymorphic attachments mean a partner may be referenced from tables the package knows nothing about. Check the migrations for foreign-key cascade and soft-delete semantics before anyone deletes anything:

grep -rn "foreignId\|foreign(\|cascadeOnDelete\|softDeletes" vendor/mybizna/partner/database/migrations/

If partners are hard-deleted while your invoices still morph-reference them, you get orphaned references that render as null in views and break PDF/invoice generation months later. Safer default: soft-delete partners in your app layer, and add a "can this partner be deleted?" policy that counts references.

Multi-tenancy is your responsibility to verify. A partner table is a core entity; if the package doesn't apply a tenant global scope, every controller you write must scope manually, and a single missed where('tenant_id', ...) is a data leak, not a bug. Test the scope explicitly:

// In a test, acting as tenant B, assert tenant A's partner is invisible
$this->actingAs($tenantBUser)
     ->get('/partners')
     ->assertDontSee('Acme Industrial Ltd');

If no global scope exists, add one via a package model subclass or a Builder::macro — but do it once, centrally, not per query.

Validation drift on type/status strings. If the package stores type as a plain string column, nothing at the database level stops 'Customer' vs 'customer'. Normalize on write (lowercase, trim) in a mutator or form request, and treat the config keys as canonical. A where('type', ...) query silently returns zero rows otherwise — and it will look like an indexing problem.

N+1 in the registry view. The default failure mode for a package like this is $partner->contacts->first() inside a Blade loop. Eager-load, and check the migrations for an index on any column you filter or sort by (name, status, type, and the morph columns). Missing indexes on a partner table are rarely a throughput problem but they are a predictable page-latency problem as the list grows.

Extension points, in order of safety: publish and edit config → register policies/gates in your app → listen to model events (Partner::created(...) for audit logs or outbound sync) → subclass the model and swap it via config if the package supports a model map → fork as a last resort. Do not edit files in vendor/: the next composer install will erase your work and it will not appear in any diff review.

Debugging checklist when something behaves oddly:

Partner::toSql();                          // see the unscoped query
Partner::getGlobalScopes();               // is a tenant scope applied — and is it the one you expect?
Partner::first()->getRelations();         // which relations actually exist
DB::listen(fn ($q) => logger($q->sql));    // trace what the package issues on your behalf

If a list is empty when you know data exists, check global scopes before you check anything else.

Uninstall is genuinely one-way. Once invoices, orders, and any external sync reference partner rows, removing this package means migrating that data out under your own model. Treat the pilot as a commitment gate, not a trial, and only proceed past it once you've (a) confirmed the version intent with the maintainer and (b) seen the next release ship an actual human-readable changelog.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style