Skip to content

Instantly share code, notes, and snippets.

@sudiptasharif
Created April 15, 2026 03:54
Show Gist options
  • Select an option

  • Save sudiptasharif/187be5871791ecc99cb545f4c745dc9c to your computer and use it in GitHub Desktop.

Select an option

Save sudiptasharif/187be5871791ecc99cb545f4c745dc9c to your computer and use it in GitHub Desktop.
PHP Fundamentals

PHP Fundamentals — Personal Notes

Based on laravel.com/learn/php-fundamentals PHP Version: 8.5.4 (Homebrew) All examples tested and verified in PhpStorm


Chapter 1 — Variables & Types

What is a Variable?

A variable stores data that we can use later while the program is running. Always starts with a $ dollar sign.

$status = 404;
$name = "Sharif";

Basic Data Types

Type Description Example
int Whole numbers $age = 30;
float Decimal numbers $price = 9.99;
string Text $name = "Sharif";
bool True or false $isActive = true;
null Exists but has no value $data = null;
array Collection of values $colors = ['red', 'blue'];
object Instance of a class $user = new User();

Dynamic Typing

PHP figures out the type automatically — you do not declare it. The same variable can hold different types at different times.

$data = "hello";  // String
$data = 3.14;     // Float
$data = true;     // Boolean

Single vs Double Quoted Strings

Double quotes allow string interpolation — variables are replaced with their values. Single quotes treat everything as plain text.

$animal = 'cat';

echo "It's a big $animal";   // Output: It's a big cat
echo 'It\'s a big $animal';  // Output: It's a big $animal

Use curly braces when variable is next to other text with no space:

echo "It's a big {$animal}s"; // Output: It's a big cats

Type Juggling

PHP automatically converts types based on the operator used.

$status = "404" + 4;   // Output: 408  (+ makes PHP think "number")
$status = 404 . "4";   // Output: "4044" (. makes PHP think "text")

⚠️ PHP 8.x behaviour — PHP 8 shows a warning for non-numeric arithmetic automatically, even without declare(strict_types=1). PHP 7 was completely silent.


strict_types

Enforces strict types when calling functions with type declarations. Does NOT stop type juggling in plain arithmetic.

declare(strict_types=1);

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

addNumbers("5", 10); // TypeError in strict mode ❌

Constants

Values that cannot change during execution. No dollar sign. Written in ALL_CAPS.

const PI = 3.14;           // Modern way — preferred in PHP 8
define('PI', 3.14);        // Older way — also valid

// Typed constant — PHP 8.3+
const float PI = 3.14159265358979323846;
Variable Constant
Syntax $PI = 3.14 const PI = 3.14
Can change? ✅ Yes ❌ Never
Dollar sign? ✅ Yes ❌ No
Naming style $camelCase ALL_CAPS

Chapter 2 — Arrays

What is an Array?

A variable that stores a collection of related data. Elements are accessed via an index starting at zero.

$colors = ['red', 'green', 'blue'];

echo $colors[0]; // red
echo $colors[1]; // green
echo $colors[2]; // blue

Associative Arrays

Uses named keys instead of numbers. Key and value are connected with the => arrow operator.

$user = [
    'name' => 'Sharif',
    'age'  => 30,
    'city' => 'Dallas',
];

echo $user['name']; // Sharif
echo $user['age'];  // 30

Two Array Types Summary

Type Key Access
Regular Number (auto) 0, 1, 2 $colors[0]
Associative Word (you choose) $user['name']

Chapter 3 — Functions

What is a Function?

A reusable piece of code that avoids repetition and organises logic.

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

echo greet('Sharif'); // Hello Sharif!

Parameters and Return Types (PHP 8)

Always declare types for parameters and return values in real applications.

function addNumbers(int $a, int $b): int {
    return $a + $b;
}
Part Meaning
int $a Parameter must be an integer
: int Function must return an integer
void Function returns nothing

echo vs return

What it does
echo Prints directly to screen, function gives nothing back
return Gives the value back so caller can use it

Prefer return in real applications — gives flexibility.


Default Arguments

Allow parameters to have a fallback value when nothing is passed. Default arguments must always come at the end of the parameter list.

function greet(int $age, string $name = 'you'): string {
    return "Hello $name you are $age!";
}

echo greet(30);          // Hello you you are 30!
echo greet(30, 'Sharif'); // Hello Sharif you are 30!

Named Arguments — PHP 8 Feature

Pass arguments in any order using the parameter name. Types are declared in the function definition, not at the call site.

function createUser(string $name, int $age, string $city): void {
    echo "$name is $age years old from $city";
}

// Order does not matter with named arguments
createUser(age: 30, city: 'Dallas', name: 'Sharif');

Chapter 4 — Loops

What is a Loop?

Repeats an action — used to avoid writing the same code multiple times, and to work through collections of data.


foreach — Most Common in Laravel

Designed specifically for iterating over arrays and collections. PHP handles the index automatically — no manual management needed.

$colors = ['red', 'blue', 'green'];

foreach ($colors as $color) {
    echo $color;
}

// With associative arrays
$user = ['name' => 'Sharif', 'age' => 30];

foreach ($user as $key => $value) {
    echo "$key: $value";
}

for Loop

Best when you know exactly how many times to repeat.

$numbers = [1, 2, 3, 4, 5];
$total = count($numbers); // calculate once, reuse — more efficient

for ($i = 0; $i < $total; $i++) {
    echo $numbers[$i];
}

while Loop

Best when you repeat until something changes but do not know how many times.

$i = 0;
while ($i < count($numbers)) {
    echo $numbers[$i];
    $i++;
}

do-while Loop

Runs at least once even if the condition is false from the start.

$i = 0;
do {
    echo $numbers[$i];
    $i++;
} while ($i < count($numbers));

Loop Summary

Loop Best Used When
foreach Looping over arrays and collections
for You know the exact number of iterations
while Repeat until something changes
do-while Must run at least once

Chapter 5 — Classes

What is a Class?

A blueprint with properties (data) and methods (behaviour). An object is a created instance of a class.

class House {
    public function __construct(
        private string $address,
        private float  $price,
    ) {}

    public function getDescription(): string {
        return "$this->address costs $this->price";
    }
}

$house = new House('123 Main St', 250000.00);
echo $house->getDescription();

Key Vocabulary

Your Words Technical Term
Data stored in a class Properties
Functions inside a class Methods
Creating an instance Instantiation
The created instance Object

Constructor Property Promotion — PHP 8

Declare and assign properties directly in the constructor. Much cleaner.

// Old way ❌ — verbose
class House {
    private string $address;
    private float $price;

    public function __construct(string $address, float $price) {
        $this->address = $address;
        $this->price = $price;
    }
}

// Modern PHP 8 way ✅ — clean
class House {
    public function __construct(
        private string $address,
        private float  $price,
    ) {}
}

Access Modifiers

Modifier Accessible From
public Anywhere — inside, outside, subclasses
protected Only inside the class and its subclasses
private Only inside the class itself
class BankAccount {
    private float $balance;        // nobody touches this directly

    public function deposit(float $amount): void {
        $this->balance += $amount; // controlled access
    }
}

$this Keyword

Refers to the current object. Used to access the class's own properties and methods.

class House {
    private string $address;

    public function __construct(string $address) {
        $this->address = $address; // $this->address = the property
                                   // $address = the parameter
    }
}

readonly Properties — PHP 8.1

Once set in the constructor, the value can never be changed. Prevents accidental modification — immutable objects.

class House {
    public function __construct(
        private readonly string $address,
        private readonly float  $price,
    ) {}
}

Getters and Setters

Getters — allow outside code to read a private property. Setters — allow outside code to change a private property.

⚠️ A setter with no validation logic adds zero value — same as making property public. ✅ A setter is valuable when it validates the incoming data.

// Useful setter — has validation
public function setPrice(float $price): void {
    if ($price < 0) {
        throw new InvalidArgumentException('Price cannot be negative');
    }
    $this->price = $price;
}
Approach When to Use
Getter only Outside code needs to read but not change
Setter with validation Outside code needs to change but with rules
No setter at all Object should not change after creation
Plain getter/setter with no logic Avoid — adds no value

Inheritance

Allows a class to extend another class and inherit its properties and methods. Used for specialisation — when something is a more specific version of a generic type.

class Duck {
    public function __construct(
        private string $name,
        private string $color
    ) {}

    public function fly(): string {
        return 'I can fly';
    }
}

class NonFlyingDuck extends Duck {
    public function fly(): string {     // Method overriding
        return 'I cannot fly';
    }
}

$mallard = new Duck('Mallard', 'Black');
$rubber  = new NonFlyingDuck('Rubber Duck', 'Yellow');

echo $mallard->fly(); // I can fly
echo $rubber->fly();  // I cannot fly

Method Overriding

Redefining a parent method in a child class to change its behaviour.


Polymorphism

Same method name, different behaviour depending on the object. This is the power of inheritance — one loop, many behaviours.

$shapes = [
    new Circle(3.5),
    new Rectangle(10, 20),
    new Circle(5.0),
];

foreach ($shapes as $shape) {
    echo $shape->getArea(); // each calculates differently
}

Calling Parent Methods

Use parent:: to call the parent class version of a method.

class NonFlyingDuck extends Duck {
    public function getDescription(): string {
        return parent::getDescription() . " (non-flying)";
    }
}

Typed Class Constants — PHP 8.3

Constants with a declared type. Use self:: to reference inside the class.

class Circle extends Shape {
    private const float PI = 3.14159265358979323846;
    private const string SHAPE = 'Circle';

    public function getArea(): float {
        return self::PI * $this->radius * $this->radius;
    }
}

PHP_EOL

The correct cross-platform way to add a new line. Works on Mac, Windows, Linux. Prefer over hardcoded "\n".

echo "Hello" . PHP_EOL;

Chapter 6 — Modern PHP

Typed Properties

Declare a type directly on a property so it can only ever hold that type. Untyped properties accept anything — typed properties throw an error if wrong type assigned.

class User {
    // Typed — safe and self-documenting ✅
    private string $name;
    private int $age;
    private float $price = 0.0;

    // Untyped — accepts anything ⚠️
    private $anything;
}

$user->name = 123;   // ❌ TypeError — must be string
$user->age = "old";  // ❌ TypeError — must be int
Untyped Typed
Wrong data assigned Silent — no error Throws immediately
Readability Hard to know what is expected Self documenting
PhpStorm support Limited autocomplete Full autocomplete

Match Expression — PHP 8

Cleaner and safer replacement for switch. Returns a value directly. Uses strict comparison ===. Throws UnhandledMatchError if no match found and no default.

$result = match($statusCode) {
    200      => 'ok',
    400, 401 => 'error',      // multiple conditions on one line
    default  => 'unknown'
};

match vs switch — Key Differences

switch match
Comparison Loose == — type juggling Strict === — type safe
Fall-through Yes — forgot break = bug No — impossible
Returns value No — needs extra variable Yes — directly
No match found Silent — does nothing Throws error immediately
Verbosity Many lines Very concise
// switch — risky ❌
switch ($color) {
    case 'red':
        $hex = '#FF0000';
        break;
    case 'green':
        $hex = '#00FF00';
        break;
    default:
        $hex = '#000000';
        break;
}

// match — clean and safe ✅
$hex = match($color) {
    'red'   => '#FF0000',
    'green' => '#00FF00',
    default => '#000000',
};

💡 Rule: In PHP 8.x — when you feel like writing switch, write match instead.


Null Safe Operator ?-> — PHP 8

Safely chain method calls on values that might be null. If any part of the chain returns null, the whole expression returns null — no error.

The problem without null safe operator:

// Verbose and ugly ❌
$planet = $spaceship->findPlanet();
if ($planet !== null) {
    $result = $planet->desc();
} else {
    $result = null;
}

With null safe operator:

// Clean and safe ✅
$result = $spaceship->findPlanet()?->desc();

Combined with null coalescing ??:

// If null at any point, use fallback value
$result = $spaceship->findPlanet()?->desc() ?? 'No planet found';

Nullable Return Types ?Type

A ? before a return type means the function can return that type OR null.

public function findPlanet(): ?Planet  // returns Planet or null
{
    if ($found) {
        return new Planet(...);
    }
    return null;
}

This is what makes the null safe operator ?-> necessary — when a method can return null, you need a safe way to chain calls on its result.


readonly Class — PHP 8.2

Makes every property in the class readonly automatically. No need to add readonly to each property individually.

// PHP 8.1 — readonly per property
class Planet {
    public function __construct(
        private readonly bool $hasAlien,
        private readonly bool $isSafe,
    ) {}
}

// PHP 8.2 — readonly the whole class ✅ cleaner
readonly class Planet {
    public function __construct(
        private bool $hasAlien,
        private bool $isSafe,
    ) {}
}

Constructor Property Promotion — PHP 8

Already covered in Chapter 5 — included here as it was part of this lesson too. Declare, type, and assign properties all in the constructor signature.

class House {
    public function __construct(
        private string $address,
        private float  $price,
    ) {}
}

Named Arguments — PHP 8

Already covered in Chapter 3 — included here as it was part of this lesson too. Pass arguments in any order using the parameter name.

createUser(age: 30, city: 'Dallas', name: 'Sharif');

Chapter 7 — Composer & Package Management

What is a Package Manager?

A tool that downloads and manages code written by other developers so you do not have to write everything yourself. Every language has one.

Language Package Manager Package Repository
PHP Composer packagist.org
JavaScript npm / yarn npmjs.com
Python pip pypi.org
Ruby Bundler rubygems.org
Rust Cargo crates.io

What is a Package?

Code someone else already wrote that solves a common problem. Instead of writing it yourself, you declare "I need this" and Composer downloads it.

Examples:

  • guzzlehttp/guzzle — makes HTTP calls to APIs cleanly
  • phpunit/phpunit — runs your automated tests
  • laravel/framework — the entire Laravel framework

What Composer Does — Three Jobs

Job 1 — Downloads packages You declare what you need, Composer fetches it from packagist.org automatically.

Job 2 — Manages versions Tracks exactly which version of each package you use so the project works the same on every computer.

Job 3 — Autoloading Maps all your classes to their file locations so PHP finds them automatically — no manual require statements ever.


The composer.json File — Your Shopping List

{
    "name": "sharif/my-project",
    "require": {
        "guzzlehttp/guzzle": "^7.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^10.0"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    }
}
Section Plain English
require Packages needed everywhere — production and local
require-dev Packages only needed on your computer for development
autoload Tells Composer where your PHP files live

require vs require-dev example:

Package Where Needed
Guzzle — makes HTTP calls Production ✅ users need this
PHPUnit — runs tests Development only ❌ users never run tests

The vendor/ Folder — The Delivery Box

When Composer downloads packages it puts them all in vendor/. Never touch this folder manually — Composer owns it completely. If deleted by accident just run composer install to rebuild it entirely.


Common Composer Commands

Command When to Use
composer install First setup, or when new packages were added to composer.json
composer require package/name Add a new package to your project
composer dump-autoload Rebuild class map after new PHP files added
composer update Upgrade package versions
composer self-update Update Composer itself

Chapter 8 — Autoloading

The Problem Before Autoloading

Without autoloading you had to manually tell every file about every other file:

// Old way — nightmare ❌
require 'Models/User.php';
require 'Models/House.php';
require 'Services/PaymentService.php';
// ... every single file, every single time

In a project with hundreds of files this becomes impossible to maintain. Move one file and everything breaks.


What Autoloading Does

One simple agreement — name your files and classes consistently and PHP finds everything automatically. No require statements ever.

// Modern way with Composer autoloading ✅
use App\Models\User;

$user = new User();  // PHP finds User.php automatically

The Autoload Map

Composer scans your project and builds a map of every class and where it lives. This map is stored in:

vendor/composer/autoload_classmap.php

It looks like this internally:

return array(
    'App\\Models\\User'      => '/src/Models/User.php',
    'App\\Models\\House'     => '/src/Models/House.php',
    'App\\Services\\Payment' => '/src/Services/PaymentService.php',
);

When PHP needs a class it looks up this map and finds the file instantly.


composer dump-autoload — What it Does

Rebuilds the autoload map completely from scratch.

Why you need it at work: When you pull new code from your team (git pull), teammates may have added new PHP files. The old cached map does not know about them yet.

git pull origin integration   # New PHP files arrive on your machine
composer dump-autoload         # Rebuild the map to include new files

Without dump-autoload — PHP crashes trying to find classes not in the old map.

When to use which command:

Command When
composer install New packages were added to composer.json
composer dump-autoload New PHP files were added but no new packages

Chapter 9 — Namespaces & PSR-4

The Problem Namespaces Solve

Without namespaces, two classes with the same name crash your application. Imagine two files both containing class User — PHP has no idea which one to use.

Namespaces give every class a unique full address — like a postal address for your code.

User              ← just a name, could be anyone
App\Models\User   ← unique full address, only one exists
App\Api\User      ← different address, completely different class

What a Namespace Looks Like in Code

Always declared as the first line after <?php.

<?php

namespace App\Models;   // ← full address of this class

class User {
    // ...
}

PSR-4 — The One Rule

PSR-4 is simply an agreement that says:

Your namespace must mirror your folder structure.

File Location Namespace Class Name
src/Models/User.php App\Models User
src/Controllers/HomeController.php App\Controllers HomeController
src/Services/PaymentService.php App\Services PaymentService

The mapping is defined in composer.json:

"autoload": {
    "psr-4": {
        "App\\": "src/"
    }
}

Plain English: "Replace src/ with App\ when building namespaces."


The Three PSR-4 Rules

Rule 1 — File name must equal Class name

User.php             →  class User
HomeController.php   →  class HomeController

Rule 2 — Namespace must equal Folder path

src/Models/          →  namespace App\Models
src/Controllers/     →  namespace App\Controllers

Rule 3 — Declare namespace at top of every file

<?php

namespace App\Models;  // always first line after <?php

class User {
}

The Mental Check — Every New File

Ask yourself two questions every time you create a new PHP file:

Q1 — Where did I save this file?

src/Services/Payment/PaymentService.php

Q2 — Does my namespace mirror that path?

namespace App\Services\Payment;  // ✅ mirrors the folder path perfectly

If both match — PSR-4 is correct. No warnings. Composer finds the class automatically.


The PSR-4 Warning at Work

When your IDE says "namespace does not follow PSR-4" it means one thing:

Your namespace does not match where your file actually lives.

You have two ways to fix it:

Fix 1 — Move the file to match the namespace

namespace App\Models\PaymentService
→ move file to src/Models/PaymentService.php

Fix 2 — Change the namespace to match the file location

File is in src/Services/
→ change namespace to App\Services

Fix whichever is easier — just make them match.


How it All Works Together — The Full Journey

use App\Models\User;
$user = new User();
  1. PHP sees App\Models\User and asks Composer "where is this?"
  2. Composer looks in autoload_classmap.php and finds the path
  3. PHP opens src/Models/User.php automatically
  4. PHP finds class User inside and uses it
  5. $user = new User() works perfectly ✅

Reading Laravel Code — Now it Makes Sense

<?php

namespace App\Http\Controllers;        // file lives in app/Http/Controllers/

use App\Models\User;                   // needs User from app/Models/User.php
use Illuminate\Http\Request;           // needs Request from vendor/ package

class HomeController extends Controller // file must be named HomeController.php
{
}

Every line now has meaning. Laravel follows PSR-4 perfectly — every file, every folder.


Simple Rules to Remember

Namespace    =   Folder path
Class name   =   File name
composer.json  =   Tells Composer where src/ starts
dump-autoload  =   Rebuilds map after new files added

Namespaces are postal addresses for your classes. PSR-4 is the rule that says your address must match where you actually live.


Quick Reference — PHP 8.x Features Used

Feature PHP Version What it Does
Constructor property promotion 8.0 Declare and assign in one line
Named arguments 8.0 Pass args in any order by name
Null safe operator ?-> 8.0 Safe chaining on nullable values
Nullable types ?Type 8.0 Return type or null
Match expression 8.0 Safe strict replacement for switch
Typed properties 8.0 Properties locked to one type
Automatic non-numeric warning 8.0 Warns on bad arithmetic
readonly properties 8.1 Immutable after construction
readonly class 8.2 All properties readonly automatically
Typed class constants 8.3 Constants with declared types

PHP Fundamentals — Final Lesson Notes

Topic: Building a CLI Weather App with Composer, Guzzle & OOP

Date: April 2026 | Student: Sharif


1. Composer & Dependency Management

Composer is PHP's package manager. It manages your project's external libraries.

Running composer init creates composer.json — your project's recipe file. Running composer install downloads packages into the vendor/ folder and creates composer.lock.

composer.json — Two Key Sections

"require": {
    "guzzlehttp/guzzle": "^7.10"
},
"require-dev": {
    "phpunit/phpunit": "^13.1"
}
Section When installed Use for
require Local AND production Packages the app needs to run
require-dev Local only Testing tools, debuggers

In production, run composer install --no-dev to skip dev packages — faster, smaller, more secure.


2. Autoloading

Composer generates vendor/autoload.php — a class map that links namespaces to file paths based on PSR-4 rules in composer.json.

require_once __DIR__ . "/vendor/autoload.php";

This one line tells PHP: "Whenever I use a class, find it automatically using Composer's map." Without it, every new WeatherService() would fail with "class not found."

PSR-4 Autoloading Rule (from composer.json)

"autoload": {
    "psr-4": {
        "Sudiptasharif\\WeatherApp\\": "src/"
    }
}

This maps the namespace Sudiptasharif\WeatherApp to the src/ folder.


3. Modern PHP OOP Features Used

readonly class (PHP 8.2+)

readonly class WeatherService { }
  • Makes all properties immutable — they cannot be changed after the constructor sets them
  • Safe, predictable, communicates intent clearly
  • Trying to change a property after construction throws an error

Constructor Property Promotion (PHP 8.0+)

public function __construct(
    private string $apiKey = 'your-key',
    private string $apiUrl = 'https://...'
) {}

The old way required 3 steps:

  1. Declare the property
  2. Accept it as a constructor parameter
  3. Assign it with $this->property = $value

Constructor promotion does all 3 in one line.


4. Calling an External API with Guzzle

Guzzle is an HTTP client library that makes calling APIs clean and easy.

$client = new Client();
$response = $client->get($this->apiUrl, [
    'query' => [
        'q'     => $city,
        'appid' => $this->apiKey,
        'units' => 'metric',
    ]
]);

The query array automatically builds URL parameters — cleaner than building URL strings manually.


5. Decoding JSON Responses

APIs return data as a JSON string. You must decode it into a PHP array to use it.

$data = json_decode($response->getBody()->getContents(), true);
Call Returns
json_decode($json) PHP object — access with ->
json_decode($json, true) PHP associative array — access with []

The true argument is essential when you want to use array syntax like $data['name'].

Important: getContents() not getContent() — easy mistake to make with Guzzle.


6. Command Line Arguments — $argv and $argc

These are global PHP variables automatically available in CLI scripts.

Variable What it contains
$argv Array of all command line inputs
$argc Count of how many arguments were passed
php weather-app.php Irving
$argv[0]  // "weather-app.php" — always the script name
$argv[1]  // "Irving"          — first user argument
$argc     // 2                 — script name + one argument

Input Validation (missing from my project — to fix)

if ($argc < 2) {
    echo "Usage: php weather-app.php <city>" . PHP_EOL;
    exit(1);
}

Always validate before using $argv[1] — without this the app crashes if no city is provided.


7. Error Handling

Throwable vs Exception

Throwable        ← catches EVERYTHING
├── Exception    ← expected problems (API failures, invalid input)
└── Error        ← serious PHP problems (null pointer, fatal errors)

Using catch (Throwable $e) is broader and safer than catch (Exception $e).

error_log() — Where Does it Go?

} catch (Throwable $e) {
    echo 'Error: Unable to Process Request.' . PHP_EOL;  // user sees this
    error_log($e->getMessage());                          // developer sees this
}

error_log() sends to different places depending on the environment:

Environment Destination
Terminal / CLI Prints to terminal
Apache server /var/log/apache2/error.log
Nginx server /var/log/nginx/error.log
Production (Paycom) Configured in php.ini

Key principle: Show users a friendly message. Log the real technical error for developers.


8. Three Things to Improve (from the lesson)

❶ Move API key to environment variable

// Bad — hardcoded in class
private string $apiKey = 'xxxxxxxxxxxxxxxxxxxxxxxx'

// Good — loaded from .env file
private string $apiKey = ''

// In .env file (never committed to git)
WEATHER_API_KEY=xxxxxxxxxxxxxxxxxxxx

Use vlucas/phpdotenv package to load .env variables.

❷ Inject Guzzle Client via Constructor

// Bad — new client created every method call, hard to test
public function getWeather(string $city): array {
    $client = new Client();
// Good — injected once, reusable, testable
private Client $client;

public function __construct(...) {
    $this->client = new Client();
}

❸ Add $argc Input Validation

if ($argc < 2) {
    echo "Usage: php weather-app.php <city>" . PHP_EOL;
    exit(1);
}

❹ Bonus — Use a CLI Framework for Better UI

Packages like symfony/console provide colored output, formatted tables, progress bars, and proper input handling for command line apps.


9. What My Code Did Well ✅

  • Used proper namespace: Sudiptasharif\WeatherApp
  • Used readonly class — modern PHP 8.2+ feature
  • Used constructor property promotion correctly
  • Added try/catch with Throwable — went beyond the tutorial
  • Added country to output — explored the API response independently
  • Used PHP_EOL for cross-platform compatibility
  • Added PHPUnit as a dev dependency — prepared project for testing
  • Separated user-facing error message from technical error log

10. Key Vocabulary

Term Meaning
Composer PHP's package/dependency manager
PSR-4 A standard rule for how namespaces map to file paths
Autoloading Automatically loading class files without manual require
Guzzle PHP HTTP client library for making API calls
JSON Data format APIs use — must be decoded to use in PHP
Associative array PHP array with named keys like $data['name']
readonly PHP keyword making properties immutable after construction
Constructor promotion Declaring, receiving, and assigning properties in one step
Throwable PHP interface that catches both Exceptions and Errors
$argv Global array of command line arguments
$argc Count of command line arguments passed
error_log() Sends error messages to PHP's error log
require-dev Composer packages only installed in development, not production

Notes compiled after completing the PHP Fundamentals course final project. Project: CLI Weather App using Composer + Guzzle HTTP + OpenWeather API

Notes by Sharif — learning Laravel the right way, understanding before building.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment