php.ini — The Configuration File That Controls How PHP Behaves

When PHP starts up, before it runs a single line of your code, it reads a configuration file. This file tells PHP how to behave — how much memory to allow, how long scripts can run, which extensions to load, how to handle errors. That file is php.ini.

Understanding php.ini is not optional knowledge for a serious PHP developer. Every time a Composer package fails, an extension is missing, or something works on one machine but not another — php.ini is almost always involved.


What Exactly is php.ini

php.ini is a plain text configuration file written in INI format. It is read by PHP once at startup. Every setting inside it controls a specific behavior of the PHP engine.

It is not PHP code. You cannot use variables, functions, or logic inside it. Every line is either a key-value setting, an extension declaration, or a comment.

memory_limit = 256M
max_execution_time = 30
upload_max_filesize = 64M

Three settings. Three behaviors controlled. That's the entire format.


Where is php.ini Located

The location depends on your setup. To find it exactly, run this in your terminal:

php --ini

Output:

Configuration File (php.ini) Path: C:\xampp\php
Loaded Configuration File:         C:\xampp\php\php.ini

The second line is what matters — the actual file being loaded right now. This is especially important because multiple PHP installations can exist on one machine, each with their own php.ini, and PHP will only load one of them.

You can also check it from a PHP file:


    <?php
    echo php_ini_loaded_file();

Or see every loaded configuration file:


    <?php
    phpinfo();

phpinfo() outputs a full page showing which php.ini was loaded, every setting currently active, and every extension currently available.


The Comment Syntax — Semicolons

INI format uses ; as the comment character. Any line starting with ; is completely ignored by PHP:

; This entire line is a comment
; PHP will never read this

This is where the enable/disable pattern for extensions comes from. When an extension line has a ; in front, PHP skips it entirely:

;extension=gd

Remove the semicolon and PHP loads the extension at startup:

extension=gd

Same line. One character difference. Completely different behavior.


What Are Extensions

PHP's core is intentionally lean. It handles the language itself — variables, loops, functions, classes, file I/O. Everything else — image processing, encryption, database drivers, internationalization — lives in extensions.

Extensions are compiled modules (.dll files on Windows, .so files on Linux/macOS) that add new functions and capabilities to PHP. They ship with PHP but are not all active by default. You enable only what your project needs.

In XAMPP on Windows, all extension files sit here:

C:\xampp\php\ext\

You'll find files like php_gd.dll, php_intl.dll, php_sodium.dll — all available but waiting to be enabled in php.ini.


The Most Important Extensions — What They Do

ext-gd — Image Processing

GD is PHP's built-in image manipulation library.

Enable it:

extension=gd

What it unlocks:


    <?php
    ob_clean();

    $image = imagecreatetruecolor(400, 200);

    $background = imagecolorallocate($image, 30, 30, 30);
    $textColor = imagecolorallocate($image, 255, 255, 255);

    imagefill($image, 0, 0, $background);
    imagestring($image, 5, 150, 90, 'Hello GD!', $textColor);

    header('Content-Type: image/png');

    imagepng($image);
    imagedestroy($image);
    exit;

Used for generating CAPTCHA images, creating thumbnails, adding watermarks, resizing uploaded photos, drawing charts — anything involving image creation or manipulation.

Laravel's image packages like intervention/image require this extension.


ext-intl — Internationalization

The intl extension provides formatting and language tools that are locale-aware — meaning they respect regional differences in how dates, numbers, and currencies are displayed.

Enable it:

extension=intl

What it unlocks:


    <?php

    $formatter = new NumberFormatter('en_IN', NumberFormatter::CURRENCY);
    echo $formatter->formatCurrency(125000, 'INR');

    $dateFormatter = new IntlDateFormatter(
        'hi_IN',
        IntlDateFormatter::LONG,
        IntlDateFormatter::NONE
    );
    echo $dateFormatter->format(new DateTime());

₹1,25,000.00
18 जून 2026

Used for multi-language applications, e-commerce platforms showing regional pricing, date formatting per locale, and sorting strings correctly across different languages. Symfony's translation component and several Laravel packages depend on this.


ext-sodium — Modern Cryptography

Sodium is a modern, high-level cryptography library. It handles encryption, decryption, digital signatures, and password hashing using algorithms that are considered secure by current standards.

Enable it:

extension=sodium

What it unlocks:


    <?php

    $key = sodium_crypto_secretbox_keygen();
    $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
    $message = "sensitive user data";

    $encrypted = sodium_crypto_secretbox($message, $nonce, $key);
    $decrypted = sodium_crypto_secretbox_open($encrypted, $nonce, $key);

    echo $decrypted;

sensitive user data

Laravel itself uses Sodium for its encryption layer. JWT libraries, OAuth packages, and any package dealing with tokens or signed payloads typically require this. It became a core PHP extension in PHP 7.2.


ext-zip — ZIP File Handling

Enable it:

extension=zip

Allows PHP to create, read, and extract ZIP archives. Composer itself requires this extension to download and extract packages. If ext-zip is disabled, Composer cannot function.


ext-mbstring — Multibyte String Functions

Enable it:

extension=mbstring

PHP's default string functions like strlen() and strtolower() count bytes, not characters. For ASCII text this works fine. For UTF-8 text — Hindi, Arabic, Chinese, emoji — one character can be 2-4 bytes. mbstring provides multibyte-aware versions:


    <?php

    $text = "नमस्ते";

    echo strlen($text);
    echo mb_strlen($text);

18
6

strlen counted bytes — 18. mb_strlen counted actual characters — 6. Almost every Laravel application needs mbstring enabled.


ext-pdo_mysql — MySQL Database Driver

Enable it:

extension=pdo_mysql

PDO (PHP Data Objects) is PHP's database abstraction layer. pdo_mysql is the MySQL-specific driver. Without it, Laravel cannot connect to a MySQL database at all. This is one of the first extensions you enable when setting up a Laravel project with XAMPP.


ext-curl — HTTP Requests

Enable it:

extension=curl

Allows PHP to make HTTP requests to external APIs and services. Laravel's HTTP client (Http::get(), Http::post()) is built on top of Guzzle, which requires cURL. Any package that talks to an external API will need this.


Key php.ini Settings Beyond Extensions

Extensions are not the only thing php.ini controls. These settings directly affect how your Laravel application runs:

Memory Limit

memory_limit = 256M

Maximum RAM a single PHP script can use. Laravel applications with large datasets, complex queries, or image processing can hit the default 128M limit. Increase it for development:

memory_limit = 512M

Max Execution Time

max_execution_time = 30

Seconds before PHP kills a running script. For CLI scripts and long-running jobs, set it to 0 (unlimited):

max_execution_time = 0

File Upload Settings

upload_max_filesize = 64M
post_max_size = 64M

Controls the maximum file size users can upload. Both settings must be updated together — post_max_size must be equal to or larger than upload_max_filesize.

Error Display

display_errors = On
error_reporting = E_ALL

For development, show all errors. For production, always turn this off:

display_errors = Off
log_errors = On
error_log = C:\xampp\php\logs\php_error.log

Multiple php.ini Files — The Common Confusion

This catches many developers off guard. XAMPP runs two different PHP processes:

Context

PHP Binary

php.ini Used

Web (Apache)

mod_php inside Apache

C:\xampp\php\php.ini

CLI (Terminal)

php.exe

C:\xampp\php\php.ini

Normally they use the same file in XAMPP. But if you have a separate PHP installation on your system — or if your PATH points to a different PHP — CLI and web could be using completely different php.ini files with different extensions enabled.

This is why the classic situation happens — something works in the browser but fails when running php artisan. Always verify which PHP and which php.ini your CLI is using:

php --ini
php -v

Both outputs should match what XAMPP shows in phpMyAdmin's PHP version display.


How Composer Uses This

When you run composer install or composer require, Composer checks which PHP extensions are active in your current php.ini. If a package declares "require": {"ext-gd": "*"} in its composer.json, Composer verifies that ext-gd is enabled before installing.

If it's not enabled, you get:

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - intervention/image requires ext-gd * -> it is missing from your system.

The fix is always the same — open php.ini, find the extension line, remove the semicolon, restart Apache from the XAMPP control panel, and run Composer again.


Workflow — Enabling an Extension Step by Step

  1. Open php.ini at C:\xampp\php\php.ini
  2. Press Ctrl + F, search for the extension name — for example gd
  3. Find the line:
;extension=gd
  1. Remove the semicolon:
extension=gd
  1. Save the file
  2. Restart Apache from XAMPP Control Panel
  3. Verify it's active:
php -m | findstr gd

If gd appears in the output, the extension is loaded and ready.


The Bottom Line

php.ini is PHP's master configuration file — it decides which features PHP has, how much memory it uses, how errors are reported, and how files are handled. Extensions are modular additions to PHP that stay disabled until you enable them by removing a single semicolon. Every time Composer complains about a missing extension, every time a package fails to install, the solution is almost always in php.ini. Knowing this file deeply means you spend less time debugging environment issues and more time writing actual code.

Closures in PHP — Anonymous Functions Explained Completely

If you've written routes in Laravel, you've already used closures without realizing it. That function () { ... } sitting inside Route::get() — that's a closure. But closures go far deeper than routes. They're one of PHP's most powerful and flexible features, used everywhere from collections to callbacks to event handling.


Start From the Beginning — What is a Function

Before understanding closures, lock in the basics.

A normal function in PHP has a name. You define it once, call it anywhere by that name:


    <?php

    function add(int $a, int $b): int
    {
        return $a + $b;
    }

    echo add(3, 4);

Output:

7

Simple. The function lives at the global level, has an identity (add), and can be called from anywhere in your code.


A Closure is a Function Without a Name

A closure is the same concept — a block of code that takes inputs and returns output — but it has no name. It's anonymous:


    <?php

    function (int $a, int $b): int {
        return $a + $b;
    };

But wait — if it has no name, how do you use it? You assign it to a variable:


    <?php

    $add = function (int $a, int $b): int {
        return $a + $b;
    };

    echo $add(3, 4);

Output:

7

The closure lives inside $add. You call it like a function using that variable. The variable is just a container holding the anonymous function.


The Three Ways to Write Closures in PHP

PHP has three distinct syntaxes for closures, each with its own use case.

1. Classic Anonymous Function

The original closure syntax, available since PHP 5.3:


    <?php

    $greet = function (string $name): string {
        return "Hello, " . $name . "!";
    };

    echo $greet("Gagan");

Hello, Gagan!

2. Arrow Function — fn

Introduced in PHP 7.4. Cleaner, single-expression syntax:


    <?php

    $greet = fn(string $name): string => "Hello, " . $name . "!";

    echo $greet("Gagan");

Same output. Arrow functions are perfect for short, single-line operations. They can't span multiple lines.

3. Static Closure

A closure that cannot access $this — used inside classes when you want an anonymous function that doesn't bind to the object:


    <?php

    $multiply = static function (int $a, int $b): int {
        return $a * $b;
    };

    echo $multiply(4, 5);

    // Output: 20


The Key Feature — Closing Over Variables

This is what makes a closure a closure — it can capture variables from its surrounding scope.

Normal named functions are isolated. They cannot see variables defined outside them:


    <?php

    $discount = 10;

    function calculatePrice(int $price): int
    {
        return $price - $discount;
    }

This throws an error — $discount is not visible inside the named function.

A closure solves this with the use keyword:


    <?php

    $discount = 10;

    $calculatePrice = function (int $price) use ($discount): int {
        return $price - $discount;
    };

    echo $calculatePrice(100);

90

The closure "closed over" $discount from the outer scope — captured it, made it available inside. This is literally where the name "closure" comes from.

Capture by Value vs Capture by Reference

By default, use captures the value at the time the closure is defined — not when it's called:


    <?php

    $discount = 10;

    $calculatePrice = function (int $price) use ($discount): int {
        return $price - $discount;
    };

    $discount = 50;

    echo $calculatePrice(100);

90

$discount was 10 when the closure was defined. Changing it later has no effect inside the closure.

To capture by reference — so changes reflect inside the closure — use &:


    <?php

    $discount = 10;

    $calculatePrice = function (int $price) use (&$discount): int {
        return $price - $discount;
    };

    $discount = 50;

    echo $calculatePrice(100);

50

Now the closure sees the live value of $discount.

Arrow Functions Capture Automatically

Arrow functions (fn) don't need use at all — they automatically capture all variables from the outer scope by value:


    <?php

    $discount = 10;

    $calculatePrice = fn(int $price): int => $price - $discount;

    echo $calculatePrice(100);

90

No use needed. This is one of the main reasons arrow functions are preferred for short operations.


Closures as Arguments — The Real Power

The most common and powerful use of closures is passing them as arguments to other functions. This is called a callback.

With Array Functions

PHP's built-in array functions accept closures as callbacks:


    <?php

    $numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    $evens = array_filter($numbers, fn(int $n): bool => $n % 2 === 0);

    $doubled = array_map(fn(int $n): int => $n * 2, $numbers);

    $sum = array_reduce($numbers, fn(int $carry, int $n): int => $carry + $n, 0);

    print_r($evens);
    print_r($doubled);
    echo $sum;

[2, 4, 6, 8, 10]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
55

Each closure runs once per array element. You define the logic inline — no need to create a named function just to use it once.

With usort

Sorting with custom logic:


    <?php

    $users = [
        ['name' => 'Ravi', 'age' => 30],
        ['name' => 'Gagan', 'age' => 25],
        ['name' => 'Amit', 'age' => 28],
    ];

    usort($users, fn(array $a, array $b): int => $a['age'] <=> $b['age']);

    foreach ($users as $user) {
        echo $user['name'] . '' . $user['age'] . PHP_EOL;
    }

Gagan — 25
Amit — 28
Ravi — 30

Closures in Laravel — Where You've Already Seen Them

In Routes


    <?php

    Route::get('/posts', function () {
        return "Showing all posts";
    });

That function () { ... } is a closure passed as the second argument to Route::get(). Laravel stores it and calls it when someone visits /posts.

In Collections

Laravel's Collection class is built around closures:


    <?php

    $posts = collect([
        ['title' => 'PHP Basics', 'published' => true],
        ['title' => 'Laravel Setup', 'published' => false],
        ['title' => 'Eloquent ORM', 'published' => true],
    ]);

    $published = $posts
        ->filter(fn(array $post): bool => $post['published'])
        ->map(fn(array $post): string => strtoupper($post['title']))
        ->values();

    print_r($published->toArray());

['PHP BASICS', 'ELOQUENT ORM']

Every filter(), map(), reject(), sortBy(), each() call in Laravel Collections takes a closure. The entire fluent chaining system is built on closures being passed and executed per item.

In Middleware


    <?php

    Route::get('/dashboard', function () {
        return view('dashboard');
    })->middleware(function ($request, $next) {
        if (!auth()->check()) {
            return redirect('/login');
        }
        return $next($request);
    });

The middleware itself can be a closure — a function that receives the request, does something, and passes it forward.

In Event Listeners


    <?php

    Event::listen('user.registered', function (User $user): void {
        Mail::to($user->email)->send(new WelcomeMail($user));
    });


Returning a Closure From a Function

Closures can also be returned from functions — this creates what's called a higher-order function:


    <?php

    function multiplier(int $factor): Closure
    {
        return fn(int $number): int => $number * $factor;
    }

    $double = multiplier(2);
    $triple = multiplier(3);
    $tenX = multiplier(10);

    echo $double(5);
    echo $triple(5);
    echo $tenX(5);

10
15
50

multiplier() returns a closure, not a value. Each call to multiplier() creates a new closure that has captured a different $factor. You now have three separate functions built from one factory function.

This pattern is used heavily in middleware pipelines, validation rule builders, and query scopes.


Closures Inside Classes — Binding $this

When you create a closure inside a class, it can access the object's properties via $this automatically:


    <?php

    class Cart
    {
        private float $taxRate = 0.18;
        private array $items = [];

        public function addItem(string $name, float $price): void
        {
            $this->items[] = ['name' => $name, 'price' => $price];
        }

        public function getTotal(): float
        {
            $taxRate = $this->taxRate;

            $total = array_reduce(
                $this->items,
                fn(float $carry, array $item): float => $carry + $item['price'],
                0.0
            );

            return $total + ($total * $taxRate);
        }
    }

    $cart = new Cart();
    $cart->addItem('Laptop', 999.00);
    $cart->addItem('Mouse', 29.00);

    echo $cart->getTotal();

1213.04

Closure vs Named Function — When to Use Which

Situation

Use

Used once, short logic

Closure / Arrow function

Used in multiple places

Named function

Passed as callback to array functions

Arrow function

Complex multi-line logic

Named function or method

Route handler in small apps

Closure

Route handler in real applications

Controller method

Laravel Collection operations

Arrow function

Event listeners and hooks

Closure



The Complete Mental Model

Named Function     → Has a name, global scope, called by name
Closure            → No name, assigned to variable or passed directly
Arrow Function     → Closure shorthand, auto-captures outer variables
Higher-Order Fn    → A function that takes or returns another function

Every time you see function () { } or fn() => in PHP — that is a closure. Whether it's inside a route, a collection chain, an array function, or an event listener — same concept, same rules, different context.


The Bottom Line

A closure is simply a function that exists without a name. What makes it powerful is the combination of three things — it can be stored in a variable, passed as an argument to another function, and capture variables from the scope where it was defined. These three abilities together enable an entirely different style of programming in PHP — one that Laravel's entire collection system, routing layer, and event system is built on top of. Once closures click, a huge portion of Laravel's design suddenly becomes obvious.

php.ini — The Configuration File That Controls How PHP Behaves

When PHP starts up, before it runs a single line of your code, it reads a configuration file. This file tells PHP how to behave — how much m...