Skip to content
View malikad778's full-sized avatar
💭
Coding Solutions
💭
Coding Solutions

Highlights

  • Pro

Block or report malikad778

Block user

Prevent this user from interacting with your repositories and sending you notifications. Learn more about blocking users.

You must be logged in to block users.

Content in all repositories owned by your account will be closed.
Maximum 250 characters. Please don’t include any personal information such as legal names or email addresses. Markdown is supported. This note will only be visible to you.
Report abuse

Contact GitHub support about this user’s behavior. Learn more about reporting abuse.

Report abuse
malikad778/readme.md

Backend Engineer  ·  Laravel  ·  PHP 8.3  ·  Multi‑tenant SaaS

Six years of PHP, most of it on the unglamorous half. The queue that has to drain. The webhook that cannot return a 502. The migration that would have taken a table lock at 4pm on a Friday.

I write the boring, load‑bearing code, and the static analysis that keeps other people from breaking it.

Portfolio LinkedIn The Regret Index Email


Proposal SaaS

Turning a delivered single‑tenant product into a real multi‑tenant one

I shipped this for one client: GrapesJS canvas, a ~900‑line server renderer, headless‑Chrome PDF export, cryptographic sealing, in‑browser signature capture. It works. Now I am pulling the single‑tenant assumptions out of it one at a time, which is turning out to be harder than building it was.

The editor was never the hard part. Tenancy is. Every B2B leak postmortem I have read comes down to the same thing: someone treated isolation as a query filter instead of a boundary the query cannot escape.

flowchart TD
    C["Company<br/><i>tenant · billing entity</i>"]
    T["Team<br/><i>grouping inside a company</i>"]
    U["User<br/><i>1 company · 0..n teams</i>"]
    R1["Owner · Admin · Member"]
    R2["team_admin · member"]

    C -->|"SECURITY BOUNDARY<br/>never removed"| T
    T -->|"VISIBILITY FILTER<br/>layered on top"| U
    U -.company role.-> R1
    U -.team role.-> R2

    classDef sec fill:#132E5C,stroke:#1F6FEB,stroke-width:2px,color:#F0F6FC
    classDef vis fill:#2A1B4D,stroke:#8957E5,stroke-width:2px,color:#F0F6FC
    classDef ent fill:#161B22,stroke:#30363D,color:#C9D1D9
    classDef role fill:#0D1117,stroke:#21262D,color:#8B949E
    class C sec
    class T vis
    class U ent
    class R1,R2 role
Loading

Important

CompanyScope applies no filter at all when no tenant resolves. That sounds like the bug. It is deliberate, so console commands and migrations still work, and it is safe for exactly one reason: users.company_id is NOT NULL. An authenticated user can never resolve to null and see everything. Make that column nullable and the whole design becomes a data leak.

Five invariants I defend with tests, not comments

Invariant What breaks without it
Scope narrows, never swaps Team visibility built by replacing the company scope instead of layering on it. This is the classic leak.
Fails open, deliberately Console commands and migrations stop working. Safe only while users.company_id stays NOT NULL.
Roles are per company Spatie's team_foreign_key points at company_id. Point it at teams.id and roles leak across tenants.
Authorisation self‑scopes User::can() binds to the user's own company. Otherwise every check inside a job or command silently returns false.
Two locale axes users.locale is the sender's UI. proposals.locale is the signed document. A German agency sends an English proposal to a UK buyer.

This one already bit me twice, in defaultTeam() and again in provisionRoles():

- // Looks harmless. Returns an empty collection when another tenant is current,
- // which produces users with no team and no visibility. No error, no warning.
- $company->teams()->get();

+ // Team carries CompanyScope, so any walk of a company's children has to run
+ // inside that company's context, even when you already hold the model.
+ CompanyContext::for($company->id, fn () => $company->teams()->get());

153 tests, 413 assertions composer audit clean Laravel 12 PHP 8.2 Browsershot 5 MySQL 8 25 locales

The actual phase tracker  (where this sits today)

Phase 1 is not done until an isolation suite proves Company A cannot reach Company B's proposals, templates, settings, media, smart content, email templates, users or teams through any route. Nothing downstream starts before that gate passes.

  • 1a Tenancy schema migrations
  • 1b Models, BelongsToCompany, visibility scopes, factories
  • 1c Global‑singleton fixes (cache keys, unique constraints)
  • 1d Roles, permissions, policies
  • 1i Internationalisation, dual‑axis  code done, ~110 admin strings left
  • 1e Public sign‑up, registration links, invitations  in progress
  • 1f Per‑tenant credentials plus SSRF guard
  • 1g Cross‑tenant isolation suite  Phase 1 gate
  • 1.5 Decoupling from the upstream billing system
  • 2 Billing, plan limits, reminders
  • 3 Approvals and multi‑signer

Debt I have written down rather than pretended away: the PDF endpoint regenerates with headless Chrome on every download. Sealed proposals are immutable, so that is pure waste, and it becomes the dominant hosting cost the moment a free tier exists. It gets cached before launch, not after.

Translation policy: formatting works for all 25 locales, but reviewed strings ship for en and de only. A proposal is a quasi‑legal document somebody signs. A mistranslated accept, decline or expiry clause is a dispute, not a typo, so machine translation does not go near the client‑facing axis.


Meta Lead Ads to WhatsApp

Webhook ingestion that does not drop leads

Meta does not care about your queue depth. Return slowly enough, often enough, and it marks the endpoint unhealthy and throttles the campaign. So nothing does real work on the request thread.

sequenceDiagram
    autonumber
    participant M as Meta Lead Ads
    participant W as Webhook endpoint
    participant D as MySQL
    participant Q as Redis · Supervisor
    participant A as WhatsApp Cloud API

    M->>W: POST lead payload
    W->>W: verify X-Hub-Signature-256<br/>via hash_equals()
    W->>D: INSERT (unique meta_leadgen_id)
    Note over D: replay collides here,<br/>not in application code
    W-->>M: 200 in milliseconds
    W->>Q: dispatch job
    Q->>A: send templated message
    A--xQ: 5xx or rate limit
    Q->>Q: exponential backoff, time-bounded
    Q->>A: retry
    A-->>Q: delivered
Loading

Two integrity gates, deliberately independent. The signature check rejects forgeries. The unique index on meta_leadgen_id makes a replay a no‑op at the database level. I want the second one in the schema rather than in a service class, because schemas do not get refactored around at 2am.

Backoff is time‑bounded, not attempt‑bounded. A provider outage should degrade throughput and then recover. It should not silently exhaust retries and drop a lead somebody paid for.

Meta Graph API WhatsApp Cloud API Laravel 11 Filament 3 Redis and Supervisor HMAC SHA-256


Solo founder. My architecture, my pager.

Log a decision, revisit it on a schedule, find out whether your instincts actually calibrate. Backed by Google Cloud for Startups and MongoDB for Startups.

Building it alone taught me more about cost than about code. Embeddings are cheap to call and expensive to call badly, and the difference is entirely in what you cache.

Next.js MongoDB Atlas OpenAI embeddings MCP server Stripe Cloud Run Cloudflare R2


Open source

Eight packages, 83 stars, all PHP. Each one exists because I hit the problem on a real project, went looking for the fix, and did not find one that worked properly.

stars last commit

Stock sync for Laravel across Shopify, WooCommerce, Amazon and Etsy. The easy part is the webhooks. The real part is what happens when two channels sell the last unit inside the same second.

Laravel 10-12  Webhooks  Job queues

stars last commit

Watches the JSON a third party actually sends you and infers its shape over time. When a field quietly disappears or changes type, you hear about it from this instead of from a customer.

PHP 8.3+  Schema drift  MIT

stars last commit Packagist installs

Reads your migrations in CI and fails the build on the ones that take a table lock or drop a column with data behind it. Zero config. Rails has strong_migrations; Laravel did not.

AST parser  CI gate  Zero config

stars last commit Packagist installs

Orphaned listeners, unheard hooks, misspelled action names. Finds all three by reading source, without ever bootstrapping WordPress, so it is fast enough to run on every commit.

PHP CLI  WordPress  Static analysis

stars last commit

Multi‑channel dispatch for Laravel 12. When a provider starts timing out the breaker trips and reroutes, instead of parking twenty workers on a dead endpoint until the queue backs up.

Laravel 12  Circuit breaker  Telemetry

stars last commit

73 WCAG 2.1 AA rules for Blade and Livewire, aimed at the European Accessibility Act. Ships a baseline file, because a linter that turns a legacy codebase red on day one just gets switched off.

WCAG 2.1 AA  Baseline  CI

stars last commit

Makes a WordPress site readable by AI agents. Semantic chunking, FAQ extraction, media and author indexing, llms.txt and an MCP manifest, all over REST.

MCP  WordPress  REST

stars last commit

Gates OTP before the user row is written, so bots never create the account at all. Includes SMS pumping and AIT fraud defence, which is the bill most OTP plugins leave you to find out about.

WooCommerce  Fraud defence  Multisite


Stack

COREPHPLaravelFilamentLivewireTypeScript
DATAMySQLPostgreSQLMongoDB AtlasRedis and HorizonUpstash
INFRADockerCloud RunCloudflare R2NginxGitHub Actions
FRONTNext.jsReactTailwindAlpine.jsGrapesJS
APISMeta GraphWhatsApp CloudStripe and ConnectOpenAIModel Context Protocol
TESTPHPUnitnikic/php-parserMockeryPintcomposer audit

The code itself

Packagist installs for Laravel-migration-guard Total stars across owned repositories 778 contributions in 12 months

The install count is the number I actually watch. Stars are a bookmark; an install means somebody put the package in a composer.json and let it run in their build.

Language split across the eight packages, by source bytes

PHP 87.1 percentBlade 6.6 percentJavaScript 3.5 percentCSS and HTML 2.8 percent

PHP 87.1%  ·  Blade 6.6%  ·  JavaScript 3.5%  ·  CSS and HTML 2.8%

Measured across the eight packages, not every repo on the account. Counting everything puts JavaScript on top at 33.6%, which is just my portfolio site's vendored assets talking. The number that means something is the one over code other people run in their own CI.


How I work

A test that never disagrees with you is decoration. Write the one that fails when the invariant breaks, not the one that passes today. In a multi‑tenant system the tests worth having are the ones that go red the second somebody collapses two axes into one.

Fail loudly at the boundary, gracefully in the middle. Reject a malformed payload at ingestion, where it is cheap and you still have the context. The alternative is a half‑written row three services deep that a customer finds for you next week.

Write down the deviation and the reason. Plans are wrong in places. Six months later the useful artefact is not the plan, it is knowing why the code went the other way and what would have to change for the original idea to work.

Comments explain why. Code explains what. If a block needs a comment to say what it does, rewrite the block. Save the comments for the constraints you cannot see from the syntax, like a port collision that presents as an auth error.


Open to backend and platform work

Laravel · PHP · multi‑tenant SaaS · integration‑heavy systems

Based in Pakistan, working remotely.

adnanhaider0347@gmail.com  ·  codebyadnan.tech  ·  LinkedIn

Pinned Loading

  1. notification-center notification-center Public

    Enterprise-grade multi-channel notification engine for Laravel 12 with parallel dispatching, smart routing, and real-time monitoring

    PHP 8 1

  2. Laravel-migration-guard Laravel-migration-guard Public

    Catch dangerous Laravel migrations before they reach production. Static analysis, zero config. The strong_migrations equivalent for Laravel.

    PHP 18

  3. php-sentinel php-sentinel Public

    Passive API Contract Monitoring for PHP 8.3+. Detects breaking changes, json schema drift, and unexpected structural modifications in third-party REST APIs via probabilistic inference.

    PHP 18

  4. nexus-inventory nexus-inventory Public

    The ultimate Laravel package for multi-channel inventory synchronization. Connect Shopify, WooCommerce, Amazon, and Etsy to Laravel with real-time webhooks, job queues, and a unified API.

    PHP 29 6

  5. wp-hook-check wp-hook-check Public

    Static analysis for WordPress hooks. Detect orphaned listeners, unheard hooks, and typos in actions and filters without running WordPress. Faster, safer WP development.

    PHP 10

  6. blade-access blade-access Public

    Static accessibility analyzer for Laravel Blade & Livewire - 73 WCAG 2.1 AA rules, CI integration, baseline system, zero runtime dependency.

    PHP