Trongate PHP Framework Docs
Introduction
Quick Start
Basic Concepts
Understanding Routing
Intercepting Requests
Module Fundamentals
Database Operations
Templates
Helpers
Form Handling
Form Validation
Working With Files
Image Manipulation
Working With Dates & Times
Language Control
Security
Tips And Best Practices
Killed It

Preventing URL Invocation

Sometimes, you may have code that should never be invoked by navigating to a particular URL. For those situations, Trongate v2 offers the function.

With block_url(), you explicitly declare which module (or module/method combination) should be blocked from URL access, and the framework enforces it.


How The block_url() Helper Function Works

The block_url() helper function accepts a string that identifies the module or module-method pair you wish to protect.

The function accepts one argument - a string that represents one of the following:

  1. 'module' - blocks all methods in the module from URL access
  2. 'module/method' - blocks only that method from URL access

The helper compares the supplied $block_path against the current URL segments and returns a 403 Forbidden response when a match is detected.


Blocking a Single Method

To block browser access to a specific method, call block_url() at the top of the method body and pass the module-method combination via $block_path:

PHP
<?php
class Reports extends Trongate {

  public function dashboard(): void {
    echo 'Public dashboard';
  }

  public function generate_pdf(): void {
    block_url('reports/generate_pdf');
    // PDF generation logic here
  }

}

The result:

  • /reports/dashboard → ✓ Works
  • /reports/generate_pdf → ✗ Returns 403 Forbidden
  • $this->reports->generate_pdf() → ✓ Works from code

The method remains public and callable from other modules. Only direct URL invocation is blocked.


How It Works

When a request is made, inspects the supplied $block_path and compares it with the current URL segments.

In the above example, if a visitor attempts to access:

  1. /reports/generate_pdf
  2. The framework routes to Reports::generate_pdf()
  3. block_url('reports/generate_pdf') executes
  4. Segment 1 equals reports and segment 2 equals generate_pdf
  5. A 403 Forbidden response is returned and execution stops

However, when the same method is called from code:

  1. Code executes $this->reports->generate_pdf()
  2. The current URL reflects whatever page the user is actually visiting
  3. The segment comparison does not match
  4. Execution continues normally

Worth knowing: because the check happens inside the method body, the controller has already been instantiated and its constructor has already run by the time block_url() executes. For most methods this is irrelevant. If your constructor does something expensive or sensitive, see the note on the underscore-first technique below, which is enforced earlier.


Blocking an Entire Module

Some modules should never be accessed via a browser. Examples include payment processors, email handlers, background services, and internal utility modules.

To block all methods in a module, pass only the module name to via $block_path. The most appropriate place to do this is inside the constructor.

PHP
<?php
class Payment extends Trongate {

  public function __construct(?string $module_name = null) {
    parent::__construct($module_name);
    block_url('payment');
  }

  public function process_charge(int $amount): void {
    // Payment logic
  }

  public function refund(string $transaction_id): void {
    // Refund logic
  }

}

PRO-TIP: Avoiding "Ghost" 403 Errors

If you are blocking an entire module inside a constructor, it is best practice to use a literal string (e.g., block_url('payment');) rather than a dynamic variable.

This ensures that if your module is ever loaded as a service by another controller, you don't accidentally block the caller's URL. For more details on how modules interact, see Mastering Constructors.

The result:

  • /payment/process_charge → ✗ Returns 403 Forbidden
  • /payment/refund → ✗ Returns 403 Forbidden
  • $this->payment->process_charge(100) → ✓ Works from code
  • $this->payment->refund('txn_123') → ✓ Works from code

The constructor code in the example above follows the recommended pattern. A full explanation of constructors in Trongate v2 can be found at Mastering Constructors.


Form Validation Callbacks

Validation callbacks should always be protected to prevent direct browser access.

Use block_url() inside the callback and pass the appropriate module-method target via $block_path:

PHP
<?php
class Members extends Trongate {

  public function create(): void {
    $this->validation->set_rules('username', 'username', 'required|callback_username_check');

    if ($this->validation->run() === true) {
      // Create the member...
    }
  }

  public function username_check(string $str): string|bool {
    block_url('members/username_check');

    if ($str === '') {
      return true;
    }

    if (!preg_match('/^[a-zA-Z0-9_]+$/', $str)) {
      return 'The {label} can only contain letters, numbers, and underscores.';
    }

    $update_id = (int) segment(3);
    $is_available = $this->model->is_username_available($str, $update_id);

    if ($is_available === false) {
      return $update_id === 0
        ? 'The {label} is already taken. Please choose another.'
        : 'The {label} is already in use by another account.';
    }

    return true;
  }

}

This ensures the callback can only be executed by the validation engine and can never be triggered by visiting /members/username_check directly in a browser.


Do not use block_url() on module paths that appear in your custom routing configuration.

Keep it simple! Mixing URL blocking with custom routing creates unnecessary complexity and hard-to-debug behavior.

Use block_url('module/method') when:

  • A specific method should never be invoked directly via a browser
  • Writing validation callbacks
  • Creating helper methods used internally by other code

Use block_url('module') when:

  • No methods in the module should be URL-accessible
  • The module exists purely as a utility
  • You want clear, explicit, blanket protection

The Underscore-First Technique

Trongate v1 veterans will remember a simpler approach, and it remains fully supported in Trongate v2: prefix any method name with an underscore (_) and the framework will refuse to invoke it via the URL.

Any method whose name begins with an underscore can never be triggered by navigating to a URL. The framework intercepts the request before the controller is even loaded and returns a 404 Not Found response.

PHP
<?php
class Tax extends Trongate {

  public function index(): void {
    $total = $this->_calculate_tax(100);
    echo 'Total: ' . $total;
  }

  public function _calculate_tax(int $amount): int {
    return (int) round($amount * 1.2);
  }

}

The result:

  • /tax/index → ✓ Works
  • /tax/_calculate_tax → ✗ Returns 404 Not Found
  • $this->_calculate_tax(100) → ✓ Works from code
  • Modules::run('tax/_calculate_tax', [100]) → ✓ Works from code

How It Works

When a request arrives, the framework examines the second URL segment (the method name) before loading the controller file. If the segment begins with an underscore, the request is rejected immediately with a 404 Not Found response and execution stops. The method never loads and never runs.

Because enforcement happens at the routing stage - before the controller is instantiated and before its constructor runs - the protection is both automatic and earlier than block_url(), which only takes effect once the method body starts executing.

Where This Technique Genuinely Helps

The underscore-first technique carries a v1 heritage, but it isn't purely a migration convenience. It has three concrete properties that make it a defensible choice in new v2 code, not just old code:

  • Earlier interception. The block happens at the routing stage, before the controller loads or its constructor executes. block_url(), by contrast, only runs once the method body begins - meaning the constructor has already completed. If a constructor does anything expensive or sensitive, this distinction can matter.
  • Less information disclosure. A blocked underscore method returns 404 Not Found - indistinguishable, from the outside, from a URL that was never a route at all. block_url() returns 403 Forbidden, which confirms to anyone probing your URLs that the endpoint exists but is off-limits. If you'd rather not reveal your internal method surface to automated scanning, the 404 response leaks less.
  • Zero boilerplate, nothing to misconfigure. block_url() requires you to type a matching path string correctly ('module/method') - get it wrong and the method is silently left unprotected. The underscore prefix can't be misconfigured in the same way: the name is the protection, and the router enforces it uniformly.

The trade-off is naming: the underscore becomes a permanent part of the method's name, and that cost is paid at every call site ($this->_calculate_tax(), Modules::run('tax/_calculate_tax')) for as long as the method exists. It's also a less immediately self-documenting signal than an explicit block_url() call - a reader unfamiliar with the convention may not recognise the underscore as a deliberate access-control decision.

Note: The underscore is a routing convention, not a visibility marker. Underscore-prefixed methods remain public in the PHP sense - they are simply unreachable via the URL. This is what distinguishes the technique from private and protected methods, which the framework's dispatcher rejects with a clean 404 (since release 2.2026.0801) and which PHP itself refuses to call from outside the class.

Use the underscore-first technique when:

  • You are migrating a Trongate v1 codebase and wish to keep existing conventions
  • You are working on legacy code that already uses the convention
  • Interception before the constructor runs, or a 404 instead of a 403, matters for this specific method
  • You want protection with literally zero risk of misconfiguration

Use block_url() when:

  • You want an explicit, self-documenting declaration at the top of a method, decoupled from the method's name
  • You need to protect an entire module from URL access
  • You need to protect a method whose name you cannot change
  • You'd rather not pay a naming cost at every call site

PHP Method Visibility: The Simplest Guard

Since release 2.2026.0801, Trongate v2 blocks URL invocation of private and protected controller methods. The dispatcher reflects on the target method and, if it is not public, returns a clean 404 Not Found — exactly as if the page did not exist. (Before this release, such a request produced a PHP fatal error and a 500 response.)

PHP
<?php
class Tax extends Trongate {

  public function index(): void {
    $total = $this->calculate_tax(100);
    echo 'Total: ' . $total;
  }

  private function calculate_tax(int $amount): int {
    return (int) round($amount * 1.2);
  }

}

The result:

  • /tax/index → ✓ Works
  • /tax/calculate_tax → ✗ Returns 404 Not Found
  • $this->calculate_tax(100) → ✓ Works from code (in-class call)

Modules::run() limitation: Modules::run() performs no visibility check — it only tests that the method exists. Calling a private or protected method via Modules::run() from outside the class produces a PHP error, not a 404. Visibility is the strictest cage: such methods are callable only from inside their own class. If a method must stay callable from other code (for example, a validation callback or a cross-module helper), use block_url() or the underscore-first technique instead of private.

Use private (or protected) when:

  • The method is only ever called from inside its own class
  • You want the strictest possible guarantee with zero guard code — nothing URL-reachable, and not reachable via Modules::run()

Choosing Between the Three Mechanisms

All three techniques stop direct URL invocation. They differ in when the block happens, what response the visitor sees, and what remains callable. The table below summarises the differences; the guidance above explains the reasoning behind each row.

Mechanism Direct URL response Enforced at Callable from other code Callable via Modules::run()
block_url('module/method') 403 Forbidden Inside the method body (after construction) Yes Yes
Underscore-first names (_method) 404 Not Found Routing stage (before construction) Yes Yes
PHP method visibility (private / protected) 404 Not Found (since 2.2026.0801; fatal 500 before) Dispatch stage, via reflection No — own class only No — PHP error

As a starting point: reach for private/protected first if the method never needs to be called from outside its own class - it's the strictest guarantee with no guard code at all. If the method does need to stay callable from other modules, block_url() is the more explicit, self-documenting default. The underscore-first technique remains a legitimate choice alongside it - not merely a migration fallback - when its earlier interception, quieter 404 response, or zero-configuration guarantee matter for the method in question, or when you're bringing existing v1 conventions forward.

Gotcha: methods whose names contain the literal string _module (for example, submit_module) are intercepted by Trongate's asset-serving route and can never be invoked via the URL. When naming methods, avoid the _module substring.

We're continually improving the Trongate documentation. If anything is incorrect, unclear, incomplete, or could be better, we'd genuinely appreciate your input.

Share your thoughts in the Documentation Feedback.

Leave Feedback About This Page