Articles

November 28, 2025

Quick Tip: A Cleaner Way to Apply Custom Eloquent Builders in Laravel

> There are times when the built-in scope methods (scopeActive, scopeLatest, etc.) aren’t enough — especially when your model starts to accumulate lots of complex query logic. A common way to keep things clean is by creating a custom `Eloquent\Builder` class and telling your model to use it, like this: ```php use Illuminate\Database\Eloquent\Builder; class TicketEloquentBuilder extends Builder { // Your query scopes goes here... } ``` Then in your model: ```php use Illuminate\Database\Eloquent\Model; class Ticket extends Model { /** * Override parent Eloquent\Builder class */ public function newEloquentBuilder($query) { return new TicketEloquentBuilder($query); } } ``` It works — but it feels a little messy. You have to override a method, and it isn’t immediately obvious that the model is using a custom builder. ### 📌 The cleaner (and more elegant) way: PHP Attributes Laravel now supports a much more elegant approach using the `UseEloquentBuilder` attribute. No more method overriding. No cluttered model. Just this: ```php use Illuminate\Database\Eloquent\Attributes\UseEloquentBuilder; #[UseEloquentBuilder(TicketEloquentBuilder::class)] class Ticket extends Model { // That's it — no need to override anything 👌🏽 } ``` Much cleaner, more readable, and instantly clear what builder your model is using. ### 🎯 When to use this? This approach shines when: ✔ You’re encapsulating reusable query logic (active, pending, filtered, by role, etc.) ✔ You want a dedicated class for complex query functionality ✔ You value explicit, clean, and attribute-based Laravel features Of course, it’s not required — it’s simply another elegant tool Laravel gives you. Use it when it improves clarity and consistency.

November 24, 2025

Soft-Launching SignDeck — Built With Purpose, Made for Real Use

> SignDeck is a lightweight document and signature request platform built for freelancers, small agencies, and service providers—designed to remove subscription stress, credit card sign-ups, and bloated features. I didn’t set out to build another e-sign app. I just wanted an easier way to send documents, collect files, and get signatures from clients—without spending money on a subscription, without creating multiple accounts, and without clicking through features I never needed. If you’re a freelancer, consultant, recruiter, small agency, broker, or anyone who deals with clients, you’ll probably relate to this. ## The Same Pain, Over and Over Again Every time I needed a client to sign something, I found myself stuck in the same loop: > Hi, could you please sign this and return it when you get a chance? They print it, sign it, take a photo, and send it back (blurry, cropped, upside-down). Or worse — “Sorry I forgot. Can you resend the link?” And for simple document requests? “Can you send your ID and a signed form?” Follow-up. Reminder. Another reminder. I was tired of chasing people. Tired of manually reminding them. Tired of scattered email threads, screenshots, downloaded files, and PDFs named Final, Really Final, Final_v2_signed2.pdf. So naturally, I turned to existing solutions… ## But the alternatives felt like overkill I tried the big ones — DocuSign, FileInvite, Adobe Sign, RightSignature, Zoho Sign, HelloSign. They’re all great, don’t get me wrong — but after using them, I just felt this: > They’re built for enterprises. I just needed something that works. Here’s what frustrated me the most: 🚫 Registration requires credit card details – Just to try it. 💳 Monthly subscriptions – Even if I only send 2 documents a month. 🎛 Packed with features I don’t use – Audit trails, advanced fields, templates, bulk send, branding, integrations, admin workflows… 🧾 UI felt too complex for my simple needs – I just wanted to upload, assign, send, track. That’s it. So instead of forcing myself to keep using tools that weren’t built for people like me… I built something that was. ## Say hello to [SignDeck](https://getsigndeck.com) 🎉 A lightweight, clean, no-nonsense way to request documents and signatures — without forcing people to sign up, subscribe, or learn a complex system. ### **❓ What it does (simply and clearly):** 📄 Send requests — for documents or electronic signatures ✍🏼 Let people sign directly on PDF — no login, no account needed ⏰ Automatic reminders — when you send, when it’s due, after comments 🗂 Track everything in one dashboard 📎 Organized by contacts and requests 🔐 Secure — with metadata-backed signatures (identity, IP, timestamp) ### **🙏🏼 Our simple promise:** 🔴 We will never ask for your credit card during sign-up. 🔴 You’ll only pay when you actually use it. We’re building a credits-based system, not subscriptions. ### **🎯 Who is it for?** ✔ Freelancers ✔ Agencies & small studios ✔ Real estate brokers ✔ HR / onboarding ✔ Legal consultants ✔ Document-heavy small businesses ✔ Anyone tired of chasing paper If you send contracts, consent forms, onboarding documents, disclosures, agreements, client requests, or signed PDFs… SignDeck might just be the lightweight alternative you’ve been looking for. ## This isn’t a startup pitch. It’s a real need. I didn’t build this to compete. I built it because I needed it — and now I’m soft-launching because maybe others do too. We’re still refining, still improving, and still building everything manually (with care). ## Try it — it’s free and requires no credit card Just sign up, play around, send a request to yourself or a friend, and tell me what you think: 👉 [https://getsigndeck.com](https://getsigndeck.com) I’d genuinely love real feedback — not to chase investors, but to build something useful. Thanks for reading — and if you know someone who chases documents and signatures for work, feel free to share this with them. 💡 Built with purpose. Made for real use.

November 21, 2025

Protect Your Admin Panel with Cloudflare Zero Trust — For Free!

*A quick security win — no coding, no plugins, no cost.* 🎯 **Why protect your Admin Panel?** Admin URLs are like gold mines for attackers. Even if your login is secure, exposing these URLs publicly allows: - Bot scanning - Brute-force attempts - Vulnerability probing The best protection? Make them invisible to the internet. ## 🚀 Solution: Hide Admin Panel behind Cloudflare Zero Trust 🛠️ **What we'll do?** We'll block public access to `/admin` (and related URLs), and allow access only to authorized users — protected by email, Google Login, OTP or identity provider. ### Step-by-Step Setup 1️⃣ **Go to Cloudflare -> Zero Trust** - Login to Cloudflare. Then on the sidebar click **Zero Trust** - Choose the free plan, but Cloudflare will still ask you to provide payment details 2️⃣ **Manage Identity Providers** - On Zero Trust Dashboard, on the sidebar click **Integrations** -> **Identity providers** - These will provide a way for you to authenticate for your login panel. **One-time PIN** is enabled by default — this will send an OTP to the registered email address. - Of course, you can also add other ways to authenticate: ![Zero Trust Login Methods](https://marvinquezon.com/storage/uploads/screenshot-2025-11-21-at-113647-am.png) 3️⃣ **Create a Policy** - On the sidebar click **Access controls** -> **Policies** - On Policy Name: Allow Team Members (or whatever policy name you want that makes sense) - Duration: Set to default: 24 hours - Then on **Add Rules** -> **Selector** choose **Emails** - here you can add the emails of your team members who will need access to your admin panel. - Once done, scroll down and click **Save** 4️⃣ **Add your Application** - On the sidebar click **Access controls** -> **Applications** - Click **Add Application** then select **Self-hosted** - Application Name: **My Website Admin** (or whatever makes sense to you) - Click on **Add public hostname** -> Input your domain and path to admin like so ``` Domain: https://mydomain.com Path: /admin* // This will secure the admin and all related URLs ``` - Then on Access Policies -> click **Select existing policies** then apply the policy that you've created - Then scroll down to **Login Methods** and make sure **Accept all available identity providers** is turned on so that any providers you've setup on the 2nd step will be used. - Then **Save** 5️⃣ **Test** - Visit your admin panel - Instead of admin login, you'll see Cloudflare Access prompt - Provide an email that was added on Step 3 -> enter code that was sent to the email -> Cloudflare grants access - After that, you'll see your normal admin login page — but only after identity check ⭐️ **Why This Works (Even on Free Plan)** ✔️ Access rules (email-based) ✔️ One-time PIN ✔️ Google login ✔️ Protect multiple paths ✔️ Works with Laravel, WordPress, Node, etc. ## 🔚 Final Thoughts This is the fastest way to secure your admin panel — without changing any code, installing any package, or paying for a plan. *Makes your panel invisible, protected, and accessible only to you — for free.*

November 16, 2025

How to Add Cloudflare Turnstile to Laravel in 5 Minutes (No Packages Required)

If you’ve built a public-facing web app, you eventually run into the same annoying problem: bots. They fill out your forms, spam your inbox, and waste your server’s time. Most developers immediately think of Google reCAPTCHA, but there’s a cleaner option that solves the same problem without the tracking baggage: **Cloudflare Turnstile**. Turnstile is Cloudflare’s privacy-friendly CAPTCHA alternative. * ❌ No user friction. * 🚍 No weird “click the traffic lights” puzzles. (ehem.. Google) * ⏲️ And setup takes only a few minutes. In this quick guide, I’ll show you why you should use Turnstile — and how to integrate it into a Laravel app without any third-party packages. ## Why use Cloudflare Turnstile? Here are the biggest reasons I migrated to it: **1. No user frustration** Turnstile doesn’t make users solve puzzles. It verifies silently in the background unless something looks suspicious. **2. Privacy-respecting** Unlike reCAPTCHA, Turnstile doesn’t track users across the web. No profiling, no annoying consent banners. **3. Dead simple to implement** It’s literally: * Load a script * Place a widget * Verify the token server-side Done! **4. Completely free** Cloudflare offers Turnstile with unlimited usage on the free plan. ### Step 1 — Create a Turnstile Widget in Cloudflare 1. Log in to Cloudflare 2. Application Security → Turnstile 3. Click **Add Widget** 4. Set: * Name: e.g: my-domain-name-turnstile * Add Hostnames: If you already have existing hostnames setup in Cloudflare you can select it, or you can just type your domain name manually. You can also add your local domains here - very useful if you are using Herd on your local machine. * Widget Mode: Managed (recommended) * Would you like to opt for pre-clearance for this site?: Just select "No" 5. Copy your **Site Key** and **Secret Key** ### Step 2 — Add the Keys, Widget and Script into Laravel Add these to your `.env` file ``` # Cloudflare Turnstile Captcha TURNSTILE_ENABLED=true # This will be so that you can just disable it quickly, especially on your tests TURNSTILE_URL=https://challenges.cloudflare.com/turnstile/v0/siteverify TURNSTILE_SITE_KEY=Your-Turnstile-Site-Key TURNSTILE_SECRET_KEY=Your-Turnstile-Secret-Key ``` Then, configure these values on your `config/services.php` file ``` // On services.php 'turnstile' => [ 'enabled' => env('TURNSTILE_ENABLED', false), 'turnstile_url' => env('TURNSTILE_URL'), 'site_key' => env('TURNSTILE_SITE_KEY'), 'secret_key' => env('TURNSTILE_SECRET_KEY'), ], ``` Then add the Widget into your Blade template — this usually goes into public facing forms such as registration, forgot password, contact us, etc. ``` ``` Then add their script outside the body of your base template ``` ``` ### Step 3 — Create a Rule class and use it on your form validations This sample rule class below provides a way to ignore `required` validation on the Turnstile field if the `config('services.turnstile.enabled')` is set to `false`. ```

June 1, 2023

Please, please, please - use Eloquent attribute Casting

In my years of developing systems and API's for various business verticals, one of the most prominent problem I have encountered and still encountering until now are attributes that were not properly cast to their expected data type. It is always such a headache when you know you should be working with a `boolean` value but receiving a `string` instead. Or an `integer` or `float` value that is not properly mutated and left as `string`, especially when working on financial-related stuff. Or worse, an `array` of objects that is, guess what, left as `string`. In Laravel, attribute casting has vastly improved since version 7.x. We only used to have [Mutators and Accessors](https://laravel.com/docs/6.x/eloquent-mutators#array-and-json-casting) available to us but now, [class-based Custom Casts](https://laravel.com/docs/10.x/eloquent-mutators#custom-casts/) is available. And it is also rich with other sub-features such as [Value Object Casting](https://laravel.com/docs/10.x/eloquent-mutators#value-object-casting) and [Serialization](https://laravel.com/docs/10.x/eloquent-mutators#array-json-serialization). Just another reason to already bunch of reasons why I love working with Eloquent ORM. Casting is our first layer of assurance that we are getting correct data types post database query. And these framework-native features give us more than enough reason to always casts our attributes. So please, don't be lazy and utilize it, for your own sanity. **TIP:** If you don't use Eloquent when querying data from your database but still want your attributes to be cast properly? Just manually instantiate your rows instead: ``` use App\Models\Posts; $posts = \DB::table('posts')->get(); $posts = $posts->map(fn ($post) => new Post($post)); ```

July 16, 2022

Level 0: Implementing PHP Static Analysis on an 8-year-old codebase

I recently got a new opportunity to work with a company as a developer. One of my first tasks is to implement [PHP Static Analysis](https://phpstan.org) on an 8 year old-ish codebase. The task seemed daunting at first but I had 2 things going for me; first, they were using Laravel framework so we can easily pull up [Larastan](https://github.com/nunomaduro/larastan), and second, the codebase has good amount of tests which gave me a huge boost in confidence. In this blog, I aim to give you few tips when implementing static analysis on a legacy codebase, and explain to you some of the static analysis errors that I encountered and how I solved it. ## First Time If this is your first time implementing static analysis, I would suggest reading through the [PHPStan Documentation](https://phpstan.org/user-guide/getting-started) first before even installing it on your codebase. Their documentation is quite comprehensive so it should not take you very long. I would also suggest, though not necessary, to watch [Nuno Maduro's talk about Types in PHP](https://youtu.be/jObcE58UCB8) for better understanding of PHP types and Larastan. By now you should know that there are **rule levels** in PHPStan - from Level 0 to Level 9. Levels determine the strictness of the rules where 0 is the loosest and 9 is the strictest. If you're implementing it on a huge codebase you should **always start at Level 0** or you will get overwhelmed with too many errors to fix. ## Installation Installing [Larastan](https://github.com/nunomaduro/larastan) should be quick and easy, just follow through the documentation on the readme file in the repository. By now, you should have a configuration file within the root of your project directory. Mine is stored as `phpstan.neon` and looks more or less like this: ``` includes: - ./vendor/nunomaduro/larastan/extension.neon parameters: checkMissingIterableValueType: false noUnnecessaryCollectionCall: false reportUnmatchedIgnoredErrors: false # Paths to scan and analyse. paths: - app # The level: 9 is the highest level. level: 0 # Circle CI configuration. parallel: jobSize: 20 maximumNumberOfProcesses: 8 # List of errors to be ignored. ignoreErrors: - '#PHPDoc tag @var#' - '#Unsafe usage of new static#' # List of paths that are excluded. excludePaths: - tests/ ``` Once you've created the configuration file, just run `./vendor/bin/phpstan analyse`, wait for couple of seconds (might take longer depending on the size of your project) and it should show you the errors on your terminal. Let's go through some of the errors that I have encountered. ## Error Patterns ### Access to an undefined property App\Foobar::$baz. This error is self-explanatory. Within the class Foobar you are doing a `$this->baz` property call. However the property is not actually declared in the class. Although PHP allows this through dynamic property, **static analysis is protecting you from making unexpected property calls to an object**. ***Fix: Just declare the property on the class or remove it entirely.*** ### Method App\Foobar::handle() should return int but return statement is missing. Another self-explanatory error. The method `handle()` within class Foobar is expected to return something but there is no return statement. This is usually because there is a doc block above the method declaration. ```php /* * @return int */ public function handle() { // Some code without return statement... } ``` ***Fix: Add a return statement or remove the doc block.*** ### Relation 'user' is not found in App\Models\Post model. This error was a bit tricky at first, because it would still appear even though the relation `user` has been declared on the `App\Models\Post` model class. ***Fix: Add a return type on the relation method.*** ```php // In App\Models\Post class... use Illuminate\Database\Eloquent\Relations\BelongsTo; public function user() : BelongsTo // Add this.. { return $this->belongsTo(User::class); } ``` ### Deprecated in PHP 8.0: Required parameter $foo follows optional parameter $bar. Another self-explanatory error and is obviously an issue only on PHP 8.0 and up. Basically, what's happening is that there is a method within your class that looks something like this. ```php public function something($bar = null, int $foo) { // ... } ``` As you can see, `$bar` is an optional parameter while `$foo` is not. Required parameters should be at the left of optional parameters. ***Fix: Refactor the method and its usages to make sure required parameters are on the left.*** ### app/Console/Commands/Foo.php: Result of method Illuminate\Console\Command::error() (void) is used. This is because in the `handle` method of the `Foo.php` class, we have this call ```php public function handle() { // Some codes... return $this->error('...'); } ``` The issue is that the result of `$this->error()` call is void type and therefore should not be used as a return statement. ***Fix: Update return statement with the correct integer code.*** And that's pretty much all of the error patterns I have encountered implementing level 0 static analysis. On the first run, there were 380 errors found with these patterns on different parts of the codebase. Hopefully you are starting to realize the benefits and protection static analysis can give you at the very beginning. I will continue this article once I get to work on the next levels. Cheers!

May 14, 2022

Refactoring Techniques: Lookup Tables

One of the easiest and most common techniques you can reach out for when refactoring your code is a **Lookup Table**. A lookup table is basically just a table, can be objects, arrays, database tables, redis cache or whatever, that you can look in to find values. Let's take a look at some common refactoring examples. # Refactoring multiple 'OR's For our first example, let's say that we have a `User` model that has a `type` property. ``` // Imagine you have something like this... if ($user->type === 'administrator' || $user->type === 'teacher' || $user->type === 'guardian') { // Do something here... } ``` You can imagine if in the future we need to add more user type that the condition could get long. This is one perfect candidate where we can use lookup tables. Take a look... ``` // Put our values in a lookup table, then proceed with our condition... $table = ['administrator', 'teacher', 'guardian']; if (in_array($user->type, $table)) { // Do something here... } ``` We've refactored to a lookup table, in this case an array, to group our possible values. And then used the lookup table in our condition to check against the user type. With this refactor, should we need to add more user types, all we have to change is our lookup table. This is what we call **isolating the change**. And with Laravel, we can refactor this further to be much more readable, like so... ``` $table = ['administrator', 'teacher', 'guardian']; if(collect($table)->contains($user->type)) { // Do something here... } ``` Using the collection the condition becomes much more clearer that we are checking if the table contains the given user type. 👌 # Imperative vs Declarative Programming Using the same example above, imagine a scenario that when you login a user, you need to redirect them to their dedicated homepage based on their user type. You would probably have something like this... ``` public function redirectUser($user) { if ($user->type === 'administrator') { return redirect('/administrator/dashboard'); } elseif ($user->type === 'teacher') { return redirect('/teachers/schedules'); } elseif ($user->type === 'guardian') { return redirect('/guardians/learners'); } } ``` As you can see, this approach is kind of describing the step by step on how to determine where to redirect the user based on their type. When you are specifying the exact steps to get the results, this is what we call **imperative programming**. Now maybe your first instinct is to refactor using switch statements. While that would definitely work, I would say switch statements would still somehow describe the step by step of the process. Using a lookup table would be much more cleaner. Let's take a look... ``` public function redirectUser($user) { $homepages = [ 'administrator' => '/administrator/dashboard', 'teacher' => '/teachers/schedules', 'guardian' => '/guardians/learners', ]; return redirect($homepages[$user->type]); } ``` As you can see with this approach, its much more results-focused. It is able to determine the outcome without having to describe the process step by step. This is what we call **declarative programming**. 😎 Generally speaking, it's easier for human brain to describe step by step approach instead of results-based approach especially when writing alorithms. This is why declarative programming is often a result of a refactor. # As Strategy Pattern This is almost the same as the last example, but you can use lookup tables to determine strategy that you can use for certain logic. To illustrate, using the same user type example above, imagine if we have different strategies for processing payments based on the user type... ``` public function processPayment($user) { $handler; if ($user->type === 'administrator') { $handler = new BankPaymentHandler; } elseif ($user->type === 'teacher') { $handler = new GcashPaymentHandler; } elseif ($user->type === 'guardian') { $handler = new PaymayaPaymentHandler; } // Assuming the handler classes are abiding by a contract with // a process method that accepts an instance of the user... return $handler->process($user); } ``` Again, we can refactor to a more declarative programming approach using a lookup table, like so... ``` public function processPayment($user) { $strategies = [ 'administrator' => BankPaymentHandler::class, 'teacher' => GcashPaymentHandler::class, 'guardian' => PaymayaPaymentHandler::class, ]; return (new $strategies[$user->type])->process($user); } ``` Hope you've learned how to use lookup tables to refactor your code. Happy coding!

Marvin Quezon · Copyright © 2025 · Privacy · Sitemap