Constants #

Constants are values whose names speak for themselves — once defined, they can’t be changed. But in PHP, “constant” isn’t just one mechanism: there’s define(), there’s const, there are class constants, interface constants, and since PHP 8.1 there’s enum, which has taken over many use cases of traditional constants. Choosing the right one among all these options isn’t a matter of taste — each has different constraints and advantages. This article covers all the constant mechanisms in PHP, when to use each one, the built-in constants PHP provides, and the patterns to avoid to keep your code clean and maintainable.

Why Constants, Not Variables? #

Before diving into syntax, it’s important to understand why constants exist and when they’re more appropriate than variables.

The most common problem constants solve is magic numbers and magic strings — literal values scattered everywhere without context:

<?php
// ANTI-PATTERN: magic numbers and magic strings
function processOrder(array $order): void
{
    if ($order['status'] === 3) {          // 3 what?
        sendEmail($order['email'], 5);    // 5 what?
    }

    if (count($order['items']) > 50) {     // why 50?
        throw new Exception("Too many items");
    }
}

// CORRECT: self-explanatory constants
const STATUS_COMPLETED      = 3;
const CONFIRMATION_TEMPLATE = 5;
const MAX_ITEMS_PER_ORDER   = 50;

function processOrderV2(array $order): void
{
    if ($order['status'] === STATUS_COMPLETED) {
        sendEmail($order['email'], CONFIRMATION_TEMPLATE);
    }

    if (count($order['items']) > MAX_ITEMS_PER_ORDER) {
        throw new Exception("Too many items");
    }
}

Besides readability, constants offer three concrete advantages over regular variables: their values can’t be changed accidentally, they’re globally available without needing to be passed as parameters, and if you need to change their value in the future, you only change one place.

flowchart TD
    A{Does the value change\nat runtime?} -- Yes --> B[Use a Variable\n$var = value]
    A -- No --> C{Tied to\na class/domain?}
    C -- Yes --> D{PHP 8.1+?}
    D -- Yes --> E{Represents a\nset of related values?}
    E -- Yes --> F[Use an Enum\nenum Status]
    E -- No --> G[Use a Class Constant\nconst NAME = value]
    D -- No --> G
    C -- No --> H{Needed at\nconditional runtime?}
    H -- Yes --> I[Use define\ndefine name value]
    H -- No --> J[Use const\nat global level]

define() — Runtime Constants #

define() is a function that defines a constant at runtime. Because it’s a function call, its definition can be placed inside conditional blocks, loops, or functions.

<?php
// Basic syntax
define('CONSTANT_NAME', value);

// Real-world examples
define('SITE_NAME',       'Nusantara News Portal');
define('SITE_URL',        'https://news.example.com');
define('MAX_UPLOAD_SIZE', 10 * 1024 * 1024); // 10 MB in bytes
define('ENABLE_DEBUG',    true);
define('DB_PREFIX',       'app_');

Because define() executes at runtime, you can use it conditionally — for example, defining different constants depending on the environment:

<?php
// Determine the environment
$env = getenv('APP_ENV') ?: 'production';

// Define different constants per environment
if ($env === 'development') {
    define('DB_HOST',     'localhost');
    define('DB_NAME',     'app_dev');
    define('LOG_LEVEL',   'debug');
    define('CACHE_TTL',   0);          // no caching in development
} else {
    define('DB_HOST',     '10.0.1.5');
    define('DB_NAME',     'app_prod');
    define('LOG_LEVEL',   'error');
    define('CACHE_TTL',   3600);       // 1 hour in production
}

echo DB_HOST;   // localhost or 10.0.1.5, depending on the environment

define() with Arrays #

Since PHP 7, define() can store array values:

<?php
define('VALID_STATUSES', [
    'active',
    'pending',
    'verified',
]);

define('HTTP_CODES', [
    200 => 'OK',
    301 => 'Moved Permanently',
    400 => 'Bad Request',
    401 => 'Unauthorized',
    403 => 'Forbidden',
    404 => 'Not Found',
    500 => 'Internal Server Error',
]);

echo HTTP_CODES[404]; // Not Found

if (in_array($status, VALID_STATUSES)) {
    // process...
}
Although define() supports arrays, for a set of related values representing statuses or categories, an enum (PHP 8.1+) is the better choice because it provides type safety and methods that can be attached directly to the values.

const — Compile-time Constants #

The const keyword defines a constant at compile time, not runtime. This means its value must be a constant expression that can be evaluated before the script runs — it can’t be a function call or a variable.

<?php
// ✓ Valid values for const
const APP_VERSION    = '2.1.0';
const MAX_RETRY      = 3;
const PI             = 3.14159265;
const ENABLE_FEATURE = true;
const EMPTY_ARRAY    = [];

// Constant expressions are allowed since PHP 5.6
const MAX_UPLOAD_BYTES = 10 * 1024 * 1024; // 10 MB — arithmetic operators OK
const TIMEOUT_MS       = 30 * 1000;        // 30 seconds in milliseconds

// ✗ Values NOT valid for const — will cause a parse error
// const TIMESTAMP = time();      // function calls are not allowed
// const HOST      = $dbHost;     // variables are not allowed
// const PREFIX    = strtolower('APP_'); // functions are not allowed

The Difference Between const and define() #

Aspectconstdefine()
Evaluation timeCompile-timeRuntime
Inside conditionals✗ Not possible✓ Possible
Inside functions✗ Not possible✓ Possible
Array values✓ Possible✓ Possible
Values from functions✗ Not possible✓ Possible
Inside classes/interfaces✓ Possible✗ Not possible
Namespace-aware✓ Follows the namespace automatically✗ Always global
<?php
// const can't be inside a conditional block
if (true) {
    // const WRONG = 'this is an error'; // Fatal error: const declarations...
    define('RIGHT', 'this is fine');    // define() can be used anywhere
}

// const at the global level follows the namespace
namespace App\Config;

const VERSION = '1.0'; // its full name is App\Config\VERSION

// Access from outside the namespace:
echo \App\Config\VERSION;

// define() is always global regardless of namespace
define('GLOBAL_KEY', 'value'); // is it always App\Config\GLOBAL_KEY? No!
                                // it stays GLOBAL_KEY in the global namespace

Practical guidance: use const for constants defined at the file/class level whose values are static. Use define() when you need conditional logic or the value comes from a function call.


Class Constants #

Constants related to a specific class or domain concept are better placed inside the class using const. This groups constants together with the code that uses them and avoids polluting the global namespace.

<?php
class OrderStatus
{
    const PENDING    = 'pending';
    const PROCESSING = 'processing';
    const SHIPPED    = 'shipped';
    const DELIVERED  = 'delivered';
    const CANCELLED  = 'cancelled';
    const REFUNDED   = 'refunded';

    // Constants can be arrays
    const TERMINAL_STATUSES = [
        self::DELIVERED,
        self::CANCELLED,
        self::REFUNDED,
    ];

    // Methods can use the class's own constants
    public static function isTerminal(string $status): bool
    {
        return in_array($status, self::TERMINAL_STATUSES, true);
    }
}

// Access from outside the class using ::
echo OrderStatus::PENDING;     // pending
echo OrderStatus::SHIPPED;     // shipped

$status = 'delivered';
if (OrderStatus::isTerminal($status)) {
    echo "The order has finished processing";
}

// Inside the class itself, use self:: or static::
class Order
{
    private string $status;

    public function isComplete(): bool
    {
        return $this->status === OrderStatus::DELIVERED;
    }
}

Class Constant Visibility #

Since PHP 7.1, class constants support visibility modifiers just like properties and methods:

<?php
class PaymentGateway
{
    // Public — accessible from anywhere
    public const VERSION         = '3.0';
    public const SUPPORTED_CARDS = ['visa', 'mastercard', 'jcb'];

    // Protected — only this class and its subclasses
    protected const INTERNAL_KEY = 'secret-internal-key';
    protected const RETRY_DELAY  = 2; // seconds

    // Private — only this class
    private const ALGORITHM      = 'AES-256-CBC';
    private const IV_LENGTH      = 16;

    public function getAlgorithmInfo(): string
    {
        // Access private constants inside the class itself
        return self::ALGORITHM . ' (IV: ' . self::IV_LENGTH . ' bytes)';
    }
}

class MidtransGateway extends PaymentGateway
{
    public function retry(): void
    {
        // Access protected constants from a subclass
        sleep(parent::RETRY_DELAY);
    }
}

echo PaymentGateway::VERSION;          // '3.0' — public OK
// echo PaymentGateway::ALGORITHM;     // Fatal Error — private
// echo PaymentGateway::INTERNAL_KEY;  // Fatal Error — protected

Constants in Interfaces #

Interfaces can define constants that must be available in every class implementing them:

<?php
interface Cacheable
{
    // Constants in interfaces are always public
    const DEFAULT_TTL = 3600;     // 1 hour
    const MAX_TTL     = 86400;    // 24 hours
    const NO_CACHE    = 0;

    public function getCacheKey(): string;
    public function getTtl(): int;
}

class ProductCache implements Cacheable
{
    public function getCacheKey(): string
    {
        return 'products:all';
    }

    public function getTtl(): int
    {
        return self::DEFAULT_TTL; // access interface constants via self::
    }
}

// Access via the interface name or the implementing class name
echo Cacheable::DEFAULT_TTL;    // 3600
echo ProductCache::DEFAULT_TTL; // 3600 — same

Enums — Modern Constants (PHP 8.1+) #

Before PHP 8.1, a set of related values was usually represented with a set of class constants. The problem: there’s no type safety guarantee — a function accepting an OrderStatus can’t ensure the incoming value is one of the valid constants.

PHP 8.1 introduced enum, which solves this problem elegantly:

<?php
// The old way — class constants, no type safety
class LegacyStatus
{
    const ACTIVE   = 'active';
    const INACTIVE = 'inactive';
}

function changeStatus(string $status): void // accepts any string!
{
    // nothing stops the caller from passing 'anything'
}

changeStatus('active');      // OK
changeStatus('nonsense');    // also OK at the type-system level, but wrong logic!

// ---

// The modern way — Pure Enum, type-safe
enum Status
{
    case Active;
    case Inactive;
    case Pending;
}

function changeStatusV2(Status $status): void // only accepts valid Status values
{
    // the compiler and IDE know which values are valid
}

changeStatusV2(Status::Active);      // ✓ OK
// changeStatusV2('active');          // ✗ TypeError — string is not a Status
// changeStatusV2(Status::Deleted);   // ✗ Error — Deleted doesn't exist in the enum

Backed Enums — Enums with Values #

Backed enums connect each case to a scalar value (string or integer), very useful for storing in a database or sending over an API:

<?php
// String-backed enum
enum OrderStatus: string
{
    case Pending    = 'pending';
    case Processing = 'processing';
    case Shipped    = 'shipped';
    case Delivered  = 'delivered';
    case Cancelled  = 'cancelled';

    // Methods can be attached directly to the enum
    public function label(): string
    {
        return match($this) {
            OrderStatus::Pending    => 'Awaiting Payment',
            OrderStatus::Processing => 'Processing',
            OrderStatus::Shipped    => 'In Transit',
            OrderStatus::Delivered  => 'Delivered',
            OrderStatus::Cancelled  => 'Cancelled',
        };
    }

    public function isTerminal(): bool
    {
        return in_array($this, [
            self::Delivered,
            self::Cancelled,
        ], true);
    }

    public function canTransitionTo(OrderStatus $next): bool
    {
        return match($this) {
            self::Pending    => $next === self::Processing || $next === self::Cancelled,
            self::Processing => $next === self::Shipped    || $next === self::Cancelled,
            self::Shipped    => $next === self::Delivered,
            default          => false, // terminal statuses can't transition
        };
    }
}

// Usage
$status = OrderStatus::Pending;
echo $status->value;             // pending — the value stored in the DB
echo $status->name;              // Pending — the case name
echo $status->label();           // Awaiting Payment

// Create from a value (e.g. from the database)
$fromDb    = OrderStatus::from('shipped');          // OrderStatus::Shipped
$fromDbAlt = OrderStatus::tryFrom('does_not_exist');     // null (doesn't throw an error)

// Check valid transitions
$can = OrderStatus::Pending->canTransitionTo(OrderStatus::Processing); // true
$can = OrderStatus::Pending->canTransitionTo(OrderStatus::Shipped);    // false
<?php
// Integer-backed enum — useful for HTTP codes, role levels, etc.
enum HttpStatus: int
{
    case Ok                 = 200;
    case Created            = 201;
    case BadRequest         = 400;
    case Unauthorized       = 401;
    case Forbidden          = 403;
    case NotFound           = 404;
    case InternalError      = 500;

    public function isSuccess(): bool
    {
        return $this->value >= 200 && $this->value < 300;
    }

    public function isClientError(): bool
    {
        return $this->value >= 400 && $this->value < 500;
    }
}

function sendResponse(HttpStatus $status, mixed $data): void
{
    http_response_code($status->value);
    echo json_encode([
        'status'  => $status->value,
        'success' => $status->isSuccess(),
        'data'    => $data,
    ]);
}

sendResponse(HttpStatus::Ok, ['user' => 'Budi']);
sendResponse(HttpStatus::NotFound, null);

When to Use Enum vs Class Constant #

Use an Enum when:
  ✓ It represents a set of mutually exclusive values
  ✓ You need type safety — ensure functions only accept valid values
  ✓ Values need logic attached to them (methods)
  ✓ PHP 8.1+ is available in your project

Use a Class Constant when:
  ✓ The value is a single configuration, not a set of choices
  ✓ You must stay compatible with PHP < 8.1
  ✓ The value is something like a limit, timeout, or version — not a "status"

PHP Built-in Constants #

PHP provides dozens of built-in constants available without defining them. Here are the most commonly used:

Magic Constants #

Magic constants change their value depending on where they’re used — unlike regular constants:

ConstantValue
__LINE__The current line number in the file
__FILE__The full path of the current file
__DIR__The directory of the current file
__FUNCTION__The current function name
__CLASS__The current class name
__METHOD__The current method name (with the class name)
__NAMESPACE__The current namespace
__TRAIT__The current trait name
<?php
// Practical uses of magic constants

// Determine a relative path from the current file's location
$configPath = __DIR__ . '/config/database.php';
require_once $configPath; // always correct no matter where the script is called from

// Logging that automatically mentions the error location
function log(string $message, string $level = 'info'): void
{
    $timestamp = date('Y-m-d H:i:s');
    $caller    = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
    $location  = $caller['file'] . ':' . $caller['line'];
    file_put_contents(
        __DIR__ . '/storage/logs/app.log',
        "[$timestamp][$level] $message$location\n",
        FILE_APPEND
    );
}

Other Common PHP Constants #

<?php
// Version and environment information
echo PHP_VERSION;           // "8.3.x"
echo PHP_MAJOR_VERSION;     // 8
echo PHP_MINOR_VERSION;     // 3
echo PHP_OS;                // "Linux", "WINNT", "Darwin"
echo PHP_OS_FAMILY;         // "Linux", "Windows", "BSD", "Darwin"
echo PHP_SAPI;              // "cli", "apache2handler", "fpm-fcgi"
echo PHP_EOL;               // "\n" on Unix, "\r\n" on Windows
echo PHP_INT_MAX;           // 9223372036854775807 (64-bit)
echo PHP_INT_MIN;           // -9223372036854775808
echo PHP_FLOAT_MAX;         // 1.7976931348623E+308
echo PHP_FLOAT_EPSILON;     // 2.2204460492503E-16

// Path separators — useful for cross-OS paths
echo DIRECTORY_SEPARATOR;   // "/" on Unix, "\" on Windows
echo PATH_SEPARATOR;        // ":" on Unix, ";" on Windows

// Use these constants for OS-portable code
$path = __DIR__ . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'cache';

Boolean, Null, and Math Constants #

<?php
// Booleans and null already exist as built-in constants
var_dump(TRUE);   // bool(true)
var_dump(FALSE);  // bool(false)
var_dump(NULL);   // NULL

// Math constants
echo M_PI;        // 3.1415926535898 — the value of π
echo M_E;         // 2.718281828459  — Euler's number
echo M_SQRT2;     // 1.4142135623731 — square root of 2
echo INF;         // INF — infinity
echo NAN;         // NAN — Not a Number

var_dump(is_nan(sqrt(-1)));   // bool(true)
var_dump(is_infinite(log(0))); // bool(true)

// Filters and flags for built-in functions
$html  = '<script>alert("xss")</script>';
$safe  = htmlspecialchars($html, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// ENT_QUOTES and ENT_HTML5 are integer constants

// SORT flags for sorting
$data = ['10', '9', '100'];
sort($data);                       // ['10', '100', '9'] — string sort
sort($data, SORT_NUMERIC);         // ['9', '10', '100'] — numeric sort
sort($data, SORT_NATURAL);         // natural sort like a file explorer

Checking and Listing Constants #

<?php
// Check whether a constant is already defined
if (!defined('APP_ENV')) {
    define('APP_ENV', 'production');
}

// Get a constant's value from its string name (dynamically)
$constantName = 'PHP_VERSION';
echo constant($constantName); // "8.3.x" — same as echo PHP_VERSION

// List all user-defined constants (not PHP built-ins)
$userConstants = get_defined_constants(true)['user'];
foreach ($userConstants as $name => $value) {
    echo "$name = " . var_export($value, true) . "\n";
}

// List all constants including PHP built-ins
$allConstants = get_defined_constants();
// a very long array — more than 1000 built-in PHP constants

Common Constant Anti-Patterns #

<?php
// ✗ Anti-pattern 1: Constants as a substitute for config that should be in .env
define('DB_PASSWORD', 'password123'); // password in source code — dangerous!

// ✓ Solution: read from an environment variable
define('DB_PASSWORD', getenv('DB_PASSWORD') ?: throw new \RuntimeException(
    'DB_PASSWORD environment variable is not set'
));

// ---

// ✗ Anti-pattern 2: Constant names that are too generic
define('MAX', 100);     // max what?
define('HOST', 'localhost'); // host for what?
define('KEY', 'abc123');    // which key?

// ✓ Solution: specific, self-describing names
define('MAX_LOGIN_ATTEMPTS',    5);
define('REDIS_HOST',            'localhost');
define('JWT_SECRET_KEY',        getenv('JWT_SECRET'));

// ---

// ✗ Anti-pattern 3: Using class constants for a set of statuses,
//    but then having to validate manually
class Status
{
    const ACTIVE   = 1;
    const INACTIVE = 2;
    const PENDING  = 3;
}

function setStatus(int $status): void
{
    // There's no guarantee $status is one of the Status::* values
    // Someone could call setStatus(999) and there'd be no error
    $this->status = $status;
}

// ✓ PHP 8.1+ solution: use an enum for type safety
enum Status: int
{
    case Active   = 1;
    case Inactive = 2;
    case Pending  = 3;
}

function setStatusV2(Status $status): void
{
    // The compiler ensures only valid Status values can get in
    $this->status = $status->value;
}

// ---

// ✗ Anti-pattern 4: Constants that actually need to be computed
define('CACHE_EXPIRE_TOMORROW', strtotime('tomorrow')); // the value changes every day!
// This isn't a constant — its value differs every time the script runs

// ✓ Solution: use a function or property computed when needed
function getCacheExpireTomorrow(): int
{
    return strtotime('tomorrow midnight');
}

// ---

// ✗ Anti-pattern 5: Duplicating the same constant across many classes
class UserController
{
    const PER_PAGE = 20;
}
class ProductController
{
    const PER_PAGE = 20; // duplicate! change one and you must change all
}

// ✓ Solution: shared constants in one place
class Pagination
{
    const DEFAULT_PER_PAGE = 20;
    const MAX_PER_PAGE     = 100;
}

class UserController
{
    public function index(): void
    {
        $perPage = Pagination::DEFAULT_PER_PAGE;
    }
}

Summary #

  • define() vs constdefine() for runtime constants that may be conditional; const for compile-time constants at file or class level. const follows namespaces, define() is always global.
  • Class constants group related values together with the class that uses them — better than global constants for domain-specific values. Use public/protected/private visibility since PHP 7.1.
  • Enums (PHP 8.1+) are the modern replacement for class constants representing mutually exclusive sets of values — providing type safety that regular class constants can’t achieve.
  • Backed enums (enum X: string or enum X: int) make conversion to/from database values easy via ->value, ::from(), and ::tryFrom().
  • Magic constants (__FILE__, __DIR__, __CLASS__, etc.) have their values determined by context — very useful for portable file paths and logging.
  • PHP_EOL and DIRECTORY_SEPARATOR are built-in constants that make code portable across operating systems.
  • Avoid magic numbers — every meaningful literal number or string should be given a constant name so the code is easy to read and change.
  • Sensitive configuration (passwords, API keys) must not be stored as constants in source code — read from environment variables or a .env file that isn’t committed to the repository.

← Previous: Variables   Next: Data Types →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact