2.1 Modern PHP Developer - Lecture Notes
2.1 Modern PHP Developer - Lecture Notes
In this section, we talk about what PHP is, what you can expect to learn, and how to install PHP on your machines.
What is PHP?
There are no notes for this lecture.
PHP Environment
In this lecture, we learned about environments and took the time to set up an environment for PHP. First, let's understand the concept of an
environment.
In the programming world, an environment is a place where you can write, test, and run computer code. Think of it like a virtual workspace with all
the tools you need to make a program work.
Local Environment
A local environment refers to the setup you have on your personal computer or local machine, where you can write, test, and run your code. Local
environments are primarily used for development and testing purposes.
This gives you, the developer, the opportunity to break things without disrupting your users. You can freely modify and make changes before
shipping a program to the real world.
Production Environment
On the other hand, a production environment is a live environment where your code is actually deployed and running for end users.
A production environment is typically hosted on a remote server, and it is designed to handle the demands of real-world usage, including security,
scalability, and reliability. The production environment is also monitored, managed, and maintained by a team of professionals to ensure optimal
performance.
Using Replit
For this course, we'll be using Replit to help us get started. Watch the video for instructions on how to set up a PHP environment on Replit.
Professional PHP developers use Docker to manage their environments. Docker is a bit too advanced for us right now, but it will be covered in a
future lecture. For now, Replit will suffice.
Resources
XAMPP
MAMP
Laragon
Replit
Open-source software (OSS) refers to code that has been freely released to the public.
A general-purpose language is a programming language that can be utilized to develop different kinds of programs.
PHP is considered to be a scripting language because it's interpreted.
Compiled vs. Interpreted
So, what does it mean when a programming language is interpreted? Computers are only capable of understanding binary code. Binary code can
be difficult to read and write. Herein lies the problem.
For this reason, programming languages were introduced for writing instructions to be executed by a machine. While programming languages are
easier to read, computers don't understand the instructions we're giving them. Therefore, our code must be transformed into machine code. This
process is known as compilation.
A compiler and an interpreter are both tools used to translate computer code into a form that a computer can execute. The main difference
between the two is how they do it:
A compiler translates the entire source code of a program into machine code (binary) in one go and saves it as an executable file. This file
can then be run as many times as needed without the need to recompile it.
An interpreter, on the other hand, executes the code line by line, translating each line of code into machine code and then executing it
immediately. It does not generate an executable file.
Think of it this way: a compiler is like a translator who translates a book into another language, then gives you the translated book to read. An
interpreter is like a translator who translates a sentence, then reads it to you immediately.
PHP is interpreted. It's safe to say that PHP is a scripting language. However, you can also call it a programming language, and no one would
complain otherwise.
Resources
PHP GitHub
Hello World!
<h1>Hello World</h1>
Websites that deliver the same content to visitors regardless of what action the user performs are known as static websites.
On the other hand, dynamic websites generate unique content to its visitors based on their actions.
PHP Tags
In this lecture, we learned about how to enter PHP mode. By default, PHP does not process raw text or HTML. We must explicitly enter PHP mode
to begin giving machines instructions. Entering PHP mode can be done with a pair of PHP tags.
<?php ?>
<?php ?>
<?php ?>
This is because the rules of PHP mode are different from HTML mode.
Inside PHP tags, we must write valid code. In the English language, there are rules for how English is read and written. There’s a specific structure
we must follow for writing English, such as ending a sentence with a period or capitalizing the first word of a sentence. The rules for human
languages are referred to as grammar.
In a similar sense, programming languages have rules for how they’re read and written. These rules are referred to as a programming language’s
syntax.
After the echo keyword, we can write our message in pair of quotes ( "" ). Lastly, we must end our line of code with a semicolon character ( ; ).
A statement is another way of describing a single instruction given to a machine or a single line of code. Programming languages give us the
ability to communicate with a machine. Instructions can be given to a machine to perform various tasks, from sending an email to processing a
transaction.
You can think of a statement as a single sentence in the English language. Multiple sentences can be combined to create a book. In a similar
sense, multiple statements can be combined to create an application.
Resources
List of Keywords
Comments
In this lecture, we explored comments in PHP, which are similar to comments in HTML and CSS. They're notes that developers can add without
PHP trying to process the contents of a comment.
// Single-line comment
/*
Multiline Comment
*/
# Single-line comment
If you were to run the above code in a PHP file, nothing would get outputted since comments don't get processed.
Variables
In this lecture, learned how to create variables. Variables were introduced for storing data, ranging from addresses to the price of a product.
Here's an example of a variable.
$age;
Variables must always start with the $ character. This character is followed by the name. The following rules must be adhered to for names to
be valid:
In some cases, you may need to use multiple words in a variable name. Typically, there are three common naming conventions adopted by
developers.
Camel Casing - All words are capitalized except for this first word. Example: $thisIsMyAge
Pascal Casing - All words are capitalized. Example: $ThisIsMyAge
Snake Casing - All words are separated with an _ character. Example: $this_is_my_age
Assignment Operator
In this lecture, we learned how to use the assignment operator to store a value in a variable. But what is an operator? Operators are symbols that
accept a value to create/produce a new value.
The assignment operator will instruct PHP to store a value inside a variable. It's written with the = character. You can use it like so.
$age = 29;
echo $age;
This outputs:
29
We have the option of updating a variable after creating it using the same syntax. PHP is smart enough to update a variable if it has already been
created.
$age = 29;
$age = 30;
echo $age;
This outputs:
30
Keep in mind that variables are case-sensitive. If you make a typo when updating a variable, you may accidentally create a new variable. $age is
completely different from $Age .
Resources
Operators
Data Types
In this lecture, we learned about data types. Data types are categories for your data. Programming languages require data to be put into
categories for easier debugging and optimization of your program. There are two types of systems seen across programming languages.
Statically-Typed – Developers must explicitly set the type of a variable. The type may never change after declaration.
Dynamically-Typed – Developers do not need to set the type. Data types may change at any time.
PHP is considered to be a dynamically-typed language. Dynamically-typed languages are easier for beginners to learn, but there are downsides.
They can be slower since the programming language takes care of assigning the type. In addition, variables may not always have the same type
throughout the lifetime of a program, which can cause unexpected behavior.
null
bool
int
float
string
array
object
callable
resource
To get started, we looked at the var_dump() function, which outputs the data type and value of a variable. All functions are written by typing their
name, followed by a pair of parentheses. Inside the parentheses, we can pass on a value to the function.
var_dump(30);
This outputs:
int(30)
Resources
List of Functions
$data = null;
null is case-insensitive. You can also use NULL . Either solution is valid. Most developers prefer to use lowercase letters.
We can view the value and data type of a variable by using the var_dump() function like so:
var_dump($data);
$isHungry = true;
$hasPermission = false;
Alternatively, you can use uppercase letters like so: TRUE or FALSE . You'll notice that the variable names are questions. It's common practice for
variables that store booleans to be named as questions.
The integer data type can store whole numbers. The float data type can store numbers with decimal values.
Why is it called float? Since the decimal separator can be positioned anywhere in the number, it's considered to be a character that can float
anywhere within a number.
Both data types support negative numbers.
$distance = 5_000_00_0;
PHP will strip these characters away during runtime. They do not affect your program. You can insert them wherever you'd like. They're
completely optional.
$firstName = 'John';
$lastName = "Smith";
PHP supports string interpolation, which is the process of replacing a placeholder in a string with a variable. For example, we can do the
following:
$firstName = 'John';
$lastName = "$firstName Smith";
This feature is only available for strings created with double quotes. In some cases, you may want to write text immediately after a placeholder.
You can wrap the variable with curly brackets to prevent the text from being interpreted as part of the variable name.
$firstName = 'John';
$lastName = "{$firstName}™ Smith";
Lastly, we have the power to access a specific character of a string by using square brackets. PHP assigns an index to each character in a string.
The first character has an index of 0, the second character has an index of 1, and so on and so forth.
var_dump($lastName[2]);
$lastName[2] = "X";
Resources
Arrays
In this lecture, we learned about arrays. Arrays are useful for storing a collection of data. Arrays can be created by using a pair of [] as the value
for a variable like so:
Existing items can be accessed or updated by their index. Behind the scenes, PHP assigns a number to each item, starting from 0 and
incrementing by 1 for each item. For example, we can update the second item in the array like so:
$food[1] = "Chicken"; // Change "Burger" to "Chicken"
New items can be added to an array by omitting a number from the square brackets. PHP will add the item to the end of the array.
Associative Arrays
In this lecture, we learned how to assign names to keys in arrays. Instead of allowing PHP to assign numbers, you can override this behavior with
custom names to better identify each item in array.
$food = [
"john" => "Salad",
"jane" => "Burger",
"sam" => "Steak"
];
Associative arrays can be created using the => character to separate the key and value. We can access a specific item from an associative array
by the key name.
var_dump($food["john"]);
Multidimensional Arrays
In this lecture, we learned about multidimensional arrays. A multidimensional array is an array that contains more arrays. This allows you to
create a nested structure of complex data. Here's an example:
$food = [
"john" => ["Salad", "Curry"],
"jane" => "Burger",
"sam" => "Steak"
];
As you can see, the john key in the array is storing an additional array. We can access items from within a nested array by using square bracket
syntax
var_dump($food["john"][1]);
In some cases, you may accidentally attempt to access an array from within an array that doesn't exist like so.
var_dump($food["bob"][1]);
In these cases, you may get the following error: Trying to access array offset on value of type null.
The error states that the inner array doesn't exist, so it can't access the item in the array. If you ever encounter this error, you should always
double-check that you're accessing an existing array correctly.
Type Casting
In this lecture, we learned how to typecast a value, which is the process of changing a value from one datatype to another. The value itself may
change to accommodate the datatype.
Converting a value into a boolean will always produce a true value unless the value is the following:
Converting a value into a float follows the same rules as integers with the addition of the following rules:
A numeric or leading numeric string will resolve to the corresponding float value. All other strings will be evaluated to zero.
All other types of values are converted first to integers and then to a float.
A false boolean value becomes an empty string, and a true value becomes the string "1" .
Integers and floats are converted to a textual representation of those numbers.
All arrays are converted to the string "Array" .
The value NULL is always converted to an empty string.
An array is used to store a bunch of elements to be accessed later. Arrays can contain zero, one, or more elements. Therefore, converting values
of type integer, float, string, bool, and resource creates an array with a single element. The element can be accessed at the index zero within the
new array. Casting a NULL into an array will give you an empty array.
Resources
Typecasting Gist
Type Juggling
In this lecture, we learned that type juggling is a behavior in PHP where values are automatically type cast without our explicit permission. If we
pass on the wrong data type, PHP may attempt to typecast the value automatically to the correct data type.
For example, the echo keyword expects a string. If we pass in an integer, the value is typecasted into a string.
echo 50;
Resources
Echo Keyword
Arithmetic Operators
In this lecture, we learned about arithmetic operators. These operators allow us to perform math. Here's an example of using the operators for
addition, subtraction, multiplication, and division.
$data = 1 + 2 - 3 * 4 / 5;
PHP adheres to the order of operations. We can use parentheses to change the outcome of an equation like so:
$data = 1 + (2 - 3 * 4) / 5;
$data = 11 % 2; // -> 1
The exponentiation operator ( ** ) performs exponentiation where the value on the left of the operator is the base and the value on the right is the
exponent.
Whenever you're working with floats, you come across situations where the float value is unexpected. This is a problem with most programming
languages since dealing with decimal values is challenging.
Before PHP can execute our application, it must be compiled into machine code. Machine code is the only language machines are able to
understand. Programming languages were introduced to be easier to read.
During this process, float values can become broken. There are ways to avoid this, which will be explored in future lectures.
Resources
Arithmetic Operators
Floating Guide
Assignment Operators
In this lecture, we explored different ways of assigning values to variables. PHP offers variations of arithmetic operators with the assignment
operator. These operators allow you to apply mathematical operations to an existing value.
$a += 2; // $a = $a + 2;
var_dump($a); // -> 12
$a -= 2; // $a = $a - 2;
var_dump($a); // -> 10
$a *= 10; // $a = $a * 10;
var_dump($a); // -> 100
$a /= 10; // $a = $a / 10;
var_dump($a); // -> 10
$a %= 6; // $a = $a % 6;
var_dump($a); // -> 4
$a **= 4; // $a = $a ** 4;
var_dump($a); // -> 256
Resources
Assignment Operators
Comparison Operators
In this lecture, we learned about comparison operators. The comparison operators allow you to compare two values. PHP can compare values
with different types.
If you were to use == or != , the datatypes of a value are typecasted if they're not the same data type. Whereas the === and !== will not, which
can cause a comparison to fail. In most cases, you should be strict with your comparisons to avoid headaches.
PHP also has two operators comparing values that aren't equal to each other, which are != and <> . There are no differences between these
operators.
// Comparison Operators (== === != <> !== < > <= =>)
var_dump(1 == "1"); // -> true
var_dump(1 === 1); // -> true
var_dump(1 != 2); // -> true
var_dump(1 <> 2); // -> true
var_dump(1 !== "1"); // -> true
var_dump(1 < 2); // -> true
var_dump(2 > 1); // -> true
var_dump(1 <= 2); // -> true
var_dump(2 >= 1); // -> true
Resources
Comparison Operators
var_dump($a++);
var_dump($a--);
Logical Operators
In this lecture, we learned how to support multiple conditions with logical operators. Logical operators allow us to chain multiple conditions. The
&& or and operators allow us to verify that multiple conditions are truthy. The || or or operators allow only one condition to be true for the
entire expression to evaluate to true . Lastly, the ! operator allows us to check for a false value instead of a true value.
Operator Precedence
In this lecture, we learned about operator precedence. If multiple operators are used within a single expression, PHP will prioritize specific
operators over others. You can check out the link in the resource section of this lecture for the order.
$a = 5 + 2 * 10;
But what if operators have the same precedence, like multiplication and division.
$a = 5 / 2 * 10;
In this case, PHP uses associativity. It varies from operator to operator. However, it'll either start with the operator on the left or on the right to
determine which operators have higher precedence.
Some operators have non-associativity. These operators don't allow you to use them within the same expression multiple times like the < and >
operators.
$a = 5 < 2 > 8;
Lastly, you might encounter problems when using operators that have super lower precedence, like the and operator. PHP offers two operators
for performing the same thing, which is the && and and operators.
However, they have different precedences, which can lead to different results.
In the above example, the second expression evaluates to true even though the second condition fails, which should technically lead to a false
value. However, the = operator has higher precedence, so PHP will assign true to the variable before checking the second condition.
This behavior can lead to unexpected results. For this reason, you should avoid the and and or operators. Instead, use their original variations,
which are && and || , respectively.
Resources
Operator Precedence
Constants
In this lecture, we learned about another type of variable called constants. Constants are variables that can never change their value after being
created. They can be created with the const keyword.
In PHP, we have flexibility with naming things. However, there is a set of reserved keywords we should avoid to prevent conflicts.
Here's how a constant is created.
The naming rules for variables apply to constants with the exception of adding the $ character at the beginning of the name. It's common
practice to use all uppercase letters so that it's easier to identify constants. Not required but recommended.
Resources
Predefined Constants
String Concatenation
In this lecture, we learned how to combine two strings into a single string with a feature called string concatenation. Two strings can be
combined with the concatenator operator, which is written with the . character.
This feature can be helpful for inserting constants into a string since string interpolation does not support constants.
Terminology: Expressions
In this lecture, we took the time to understand some programming terminology, which will be useful in upcoming lectures. Specifically, we learned
about expression. An expression is any line of code that produces a value. It's as simple as that.
Resources
Echo
Control Structures
In this lecture, we learned how to control the logic flow of our program with If statements. An if statement accepts an expression that must
evaluate to a boolean value. If the value is true , a block of code is executed. Otherwise, nothing happens.
$score = 95;
We can also add the else if / elseif keyword to perform an alternative condition. If prior conditions fail, the conditions from these statements
will be checked. Once a condition passes, other conditions are ignored. An else statement can be added for default behavior. You can compare
values in an expression.
Resources
If Statement
Switch Statements
In this lecture, we learned an alternative solution to If statements called switch statements. Switch statements don't have as much flexibility as If
statements. They only compare values that match. If a match is found, the code below it is executed.
A switch statement can be written with the switch keyword. With this keyword, we can provide a value to match. Afterward, we can add a list of
values to compare with the value from the switch statement by using the case keyword.
$paymentStatus = 1;
switch ($paymentStatus) {
case 1:
var_dump("Success");
break;
case 2:
var_dump("Denied");
break;
default:
var_dump("Unknown");
}
In this example, we're also adding the break keyword. Once PHP finds a match, it'll execute written below the case statement. This includes code
from other case statements. To prevent that from happening, we can end the switch statement with the break keyword.
Something to keep in mind with switch statements is that data types do not have to match. For example, take the following:
$paymentStatus = "2";
switch ($paymentStatus) {
case 1:
var_dump("Success");
break;
case 2:
var_dump("Denied");
break;
default:
var_dump("Unknown");
}
The $paymentStatus variable stores a string. However, the cases are integers. Regardless, the values can still match because PHP will typecast
the values during comparison.
Match Expressions
In this lecture, we learned about match expressions, which are a feature to compare values and return the results. We can write a match
expression with the match keyword. This keyword accepts the value to compare with a list of values inside a pair of curly brackets like so.
$paymentStatus = 2;
We can add as many values to compare with. A default value can be added when a match can't be found with the default keyword. Match
expressions must always return a value. Otherwise, PHP will throw an error. It's always considered good practice to have a default value.
Something to keep in mind is that comparisons are strict. The values must have the same value and data type. If the values are the same but
different types the match won't be registered.
Functions
In this lecture, we learned about functions. Functions are blocks of code that are reusable. By using functions, we don't have to copy and paste
the same solution over multiple files.
Functions can be defined with the function keyword followed by the name, pair of parentheses, and curly brackets. Function names follow the
same rules as variable names, with the exception of the name starting with the $ character.
function getStatus()
{
$paymentStatus = 2;
var_dump($message);
}
Functions can be invoked by writing the name with a pair of parentheses. Invoking a function is the process of running the code inside the
function. By default, PHP does not execute the code unless it's invoked.
getStatus();
Function Parameters
In this lecture, we learned how to pass on data to a function by using parameters. Parameters are variables defined in a function's parentheses
that can be updated when a function is called. For example, a parameter can be defined like so.
function getStatus($paymentStatus)
{
// Some code...
}
getStatus(1);
The $paymentStatus parameter will hold the value 1. If we do not pass on data, PHP will throw an error.
We can add additional parameters by separating each parameter with a comma. In addition, parameters may have optional values. If a function is
not provided data, PHP will fall back to the default value.
In some cases, you may hear the word argument to describe a parameter. Parameters and arguments can be interchangeable terms, but there is
a difference between them. A parameter describes the variable in the function definition, whereas an argument describes the value itself passed
into the function.
if ($showMessage) {
var_dump($message);
}
return $message;
}
Keep in mind, PHP stops executing logic from within a function once a value is returned. Typically, it's advised to return a value at the end of the
function.
The value returned by the function can be stored in a variable like so:
$statusMessage = getStatus(1);
This allows us to use the message returned by the function in other areas outside of the function. Returning values is optional but can be a great
way to use data after a function has finished executing.
A function's data type can be set by adding a : followed by the type like so:
function getStatus($paymentStatus, $showMessage = true): string
In some cases, we may want to return null from our function. We can add the ? character before the return type like so:
Alternatively, our functions may not return data at all. In these cases, we can use the void data type, which was designed for functions that don't
return anything.
We're not limited to setting the return type. Parameters can be type hinted too. The data type must be added before the parameter name like so.
Multiple data types can be assigned by separating them with the | character. These are known as union types.
Lastly, if you don't want to chain data types and would rather accept any data type, you can use the mixed type.
Strict Types
In this lecture, we enforced strict types for functions by using the declare directive. This keyword allows us to enable specific PHP features. PHP
is a weakly typed language. Even if we use type hinting, that doesn't mean PHP will stop us from passing in a value with an invalid data type.
For debugging purposes, it's considered good practice to enable strict typing.
declare(strict_types=1);
Adding this bit of code will apply strict typing to a specific file. If you have another PHP file in your application, that file won't have this feature
enabled. You must add it to every file that you want strict types.
While Loop
In this lecture, we learned how to repeat a series of actions based on a condition with the while loop. The while loop accepts a condition. If the
condition evaluates to true , a block of code gets executed. This process is repeated until the condition evaluates to false .
$a = 1;
We're using a new keyword called echo , which outputs a value. Unlike the var_dump() function, the datatype is not rendered with the value.
This code creates an infinite loop. Infinite loops consume resources, which can cause a system to crash. Since the $a variable is never updated,
the condition evaluates to true . It's considered good practice to update the variable on each iteration.
$a++;
}
We're using the ++ increment operator. This operator adds 1 to a value. This should prevent the loop from running infinitely.
In some cases, you may want to execute the block of code at least once. You can use the do while loop to achieve this behavior.
do {
echo $a;
$a++;
} while ($a <= 15);
The condition in the while keyword is performed after the block of code has been executed.
For Loop
In this lecture, we looked at an alternative to the while loop called the for loop. Similar to the while loop, we can use a condition to determine
when a loop should run. However, rather than partially writing the logic pertaining to the loop outside of the parentheses, everything is centralized
in one location.
1. Initializer - A variable that can be initialized for iterating through the loop.
2. Condition - An expression that evaluates to a boolean. If true , the block of code is executed.
3. Increment - An expression that should update the variable from the first step.
PHP always executes the initializer once before checking the condition. Afterward, it'll execute the block of code. After the block of code finishes
running, the third expression is executed. PHP checks the condition and repeats the process.
In some cases, you might want to skip an iteration or stop the loop completely. You can do so with the continue or break keywords,
respectively.
echo $i;
}
A new comparison operator was introduced called == . This operator compares two values for equality. In this example, we're checking if the $i
variable is equal to 6 . If it is, the current iteration is skipped. PHP will not echo the number and move on to the next iteration.
If you want to stop the loop altogether, you can use the break keyword.
Foreach Loop
In this lecture, we learned about the foreach loop, which allows us to loop through an array. This loop accepts the array to loop through, followed
by the as keyword and a variable to store a reference to each item in the array.
In some cases, you may want the key to the current item. You can update the expression to include a variable for the key like so.
Short-Circuiting
In this lecture, we learned about a feature called short-circuiting. PHP will not bother checking other conditions if a previous condition evaluates
to false . This increases performance since PHP does not perform additional logic.
function example()
{
echo "example() invoked";
return true;
}
Since the first condition is false , the example() function is never executed. If you were to run the above code, you would not see the message
from the function appear in the output.
To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. Each band has a position
and a numeric value.
The first 2 bands of a resistor have a simple encoding scheme: each color maps to a single number.
In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands.
black: 0
brown: 1
red: 2
orange: 3
yellow: 4
green: 5
blue: 6
violet: 7
grey: 8
white: 9
The goal of this exercise is to create a way to look up the numerical value associated with a particular color band. Mnemonics map the colors to
the numbers, that, when stored as an array, happen to map to their index in the array.
More information on the color encoding of resistors can be found in the Electronic color code Wikipedia article.
Starter Code
<?php
function colorCode($color)
{
<?php
declare(strict_types=1);
return $colors[$color];
}
Starter Code
<?php
declare(strict_types=1);
<?php
declare(strict_types=1);
The tricky thing here is that a leap year in the Gregorian calendar occurs:
For example, 1997 is not a leap year, but 1996 is. 1900 is not a leap year, but 2000 is.
For a delightful, four minute explanation of the whole leap year phenomenon, go watch this youtube video.
Starter Code
<?php
declare(strict_types=1);
<?php
declare(strict_types=1);
return true;
}
Predefined Constants
In this lecture, we explored some predefined constants by PHP. These are some of the more common ones.
In addition, PHP has magic constants, which are constants that have dynamic values but cannot be updated by us. The most common magic
constants are the following:
Resources
Predefined Constants
Magic Constants
Other than how the constant is created, using the constant is similar to any other constant.
The main difference between the define() function and const keyword is that the define() function can be used in a conditional statement,
whereas the const keyword cannot.
if (!defined("FOO")) {
define("FOO", "Hello World!");
}
It's common practice to verify that a constant has been created by using the defined() function, which accepts the name of the constant to
check. If the constant isn't created, we can proceed to create one.
Unsetting Variables
In this lecture, we learned how to release memory from our program by using the unset() function. This function accepts the variable to delete
from your program. For example.
$name = "John";
unset($name);
In the above example, the $name variable gets defined, unset, and then throws an error whenever we try to reference the variable afterward since
it no longer exists in our program. PHP throws an undefined error.
In addition, you can remove specific items from an array using this method like so.
unset($names[1]);
This will remove the second item from the array but does not reindex the array. If you want to reindex the array, you can use the array_values
function.
$names = array_values($names);
You can verify the array was reindexed by using the print_r() function.
print_r($names);
This function outputs the items in array. It's different from the var_dump() function since it does not output the data types of each item in the
array.
Resources
PHP Manual
Rounding Numbers
In this lecture, we learned how to round numbers. PHP offers three functions for rounding numbers. The first of which is floor() and ceil() .
The floor() function rounds a number down, and the ceil() function rounds a number up. Both round to the nearest whole number.
The round() function allows us to round a number while also adding precision to keep a certain number of digits in the decimal portion of a
value. It accepts the number to round, precision, and whether to round up or down when the decimal value is halfway through to the next number.
ceil()
floor()
round()
Another difference between using curly brackets and keywords is that the else if keyword can't be used. We must stick with the elseif
keyword.
<?php
function getPermission() {
sleep(2);
return 2;
}
?>
In this example, the getPermission() function takes two seconds to execute cause of the sleep() function. We're calling it twice from both
conditions, which can cause our application to take longer to respond.
A better solution would be to outsource the value returned by our function in a variable so that it's only called once.
<?php
function getPermission() {
sleep(2);
return 2;
}
$permission = getPermission();
?>
Loops
In this example, the getUsers() function is called on each iteration of the loop, which needs 2 seconds to finish executing.
To fix this, we can outsource the value returned by the function in a variable called $userCount and use that variable from within the condition
like so.
function getUsers() {
sleep(2);
$userCount = count(getUsers());
for ($i = 0; $i < $userCount; $i++) {
echo $i;
}
include "example.php";
include_once "example.php";
require "example.php";
require_once "example.php";
The main difference between these keywords is that the include keyword(s) throw a warning if a file can't be found. Warnings don't interrupt the
rest of the script from running.
The require keyword(s) throws a fatal error, which does stop the rest of the script from running. The keywords with the word _once only include
a file once. If a file has been included before, it will not be included again.
Variadic Functions
A variadic function is a function that accepts an unlimited number of arguments. You can define a variadic function by adding the ... operator
before the parameter name like so.
In this example, the sum() function accepts an unlimited set of numbers. All values are stored in an array called $num . Lastly, we're calculating
the sum using the array_sum() function.
Named Arguments
In this lecture, we learned how to assign values to specific parameters by using named arguments. By default, PHP assigns values to parameters
in the order they're presented. In some cases, we may want to set specific values to specific parameters like so.
We can add the name of the parameter before the value with a : separating the parameter name and value. So, even if the order does not match,
PHP won't have a problem assigning the value to the correct parameter.
Resources
setcookies() Function
Global Variables
In this lecture, we learned how to use global variables. By default, functions do not have access to variables defined outside of the function. If we
want to use a variable, we must use the global keyword followed by a list of variables we'd like to use. Multiple variables can be specified by
separating them with a comma.
Here's an example:
$x = 5;
function foo() {
global $x;
echo $x;
}
foo();
Global variables can be convenient, but developers often avoid them. It's recommended to pass in the value to the function and have the function
return a new value. Global variables can make your application behave unreliably.
Static Variables
In this lecture, we learned about static variables. Static variables are a feature that allows variables defined in functions to retain their values after
a function has finished running.
By default, variables defined in a function are destroyed after a function is finished running. As a result, the value gets reset when the function is
called again.
By adding the static keyword to a variable, the variable will retain the value like so.
function foo() {
static $x = 1;
return $x++;
}
By storing it in a variable, we can pass on this function as an argument to another function. For example:
We're referring to the function as $callback . Callback is a common naming convention for functions that get passed into another function that
can be called at a later time.
In some cases, you may want to access variables in the parent scope. You can do so with the use keyword after the parameter list.
$multiplier = 2;
$multiply = function ($num) use ($multiplier) {
return $num * $multiplier;
};
Unlike global variables, PHP creates a unique copy of the variable, so the original variable does not get modified if you attempt to update the
value.
A shorter way of writing anonymous functions is to use arrow functions. Here's the same example as an arrow function.
Unilke before, we don't have to use the use keyword since all variables in the parent scope are accessible to the arrow function. In addition, the
code to the right of the => character is treated as an expression. The value evaluated by the expression is the return value.
Keep in mind, arrow functions cannot have a body. You may only write an expression, and arrow functions always return a value. Regular
anonymous functions can optionally return values.
Callable Type
In this lecture, we learned about the callable type, which is the data type you can add to a parameter that holds a function. Here's an example:
You have a few options when passing in a function. You can either pass in a variable that holds the function, write the function directly, or pass in
the name of a regular function.
Passing by Reference
In this lecture, we learned how to pass in a variable by reference. If we were to add the & character before the name of the parameter, PHP does
not create a unique copy of the value and assign it to the parameter.
Instead, PHP will allow us to reference the original variable through the parameter. If we modify the reference, the original variable gets modified
too.
$cup = "empty";
function fillCup(&$cupParam) {
$cupParam = "filled";
}
fillCup($cup);
In this example, the $cup variable will be set to filled since the value gets manipulated from within the fillCup() function.
Array Functions
In this lecture, we explored various functions for manipulating arrays. PHP offers a wide assortment of functions. Here are some of the most
common ones you may use from time to time.
The isset() and array_key_exists() functions can be used to verify that a specific key in a function has a value. However, the isset()
function does not consider null or false values to be acceptable.
if (isset($users[3])) {
echo "User found!";
}
In this example, the isset() function will return false since $users[3] contains a null value. Despite having a value, this index is considered
to be empty. If we want the condition to pass for arrays that have null values, we can use the array_key_exists() function.
if (array_key_exists(3, $users)) {
echo "User found!";
}
Another function worth looking at is the array_filter() function, which will allow us to filter the items in the array. By default, this function
removes empty values in an array.
$users = array_filter($users);
In the example above, the array_filter() function returns the $users array without the null value. If we want to customize the filtering, we
can pass in a callback function. This callback function will be given each item in the array and can return a boolean to determine if the value
should remain in the array.
The array_map() function can be used to modify each item in the array. It has two arguments, which are the array and a callback function that
will be given each item in the array. The callback function must return the value or a modified version of the value.
In this example, each user is run through the strtoupper() function to convert lowercase letters into uppercase.
The array_merge() function can be used to merge two or more arrays into a single array.
In this example, the second array will be appended to the array creating the following output:
We can also sort arrays using PHP's various sorting functions. Refer to the resource section for a link to a complete list of sorting functions.
The first function you'll often use is the sort() function, which sorts an array in ascending order.
sort($users);
Keep in mind, the sort() function does not return an array. Instead, it references an array and can modify the existing array.
Another thing to know is that the sort() function reindexes your array. If you wish to keep the original indexes, you can use the asort() function
instead.
asort($users);
Resources
Array Functions
array_filter() Function
array_merge() - Function
sort() Function
Sorting Arrays
Destructuring Arrays
In this lecture, we learned how to destructure an array to extract specific values. There are two ways to grab items from an array. Firstly, we can
use the list keyword like so.
echo $a;
PHP extracts the values in the order they're defined. So, the $a variable will have the value of 10 .
scandir(__DIR__);
This function returns a complete list of files and directories in a specific directory. You can use __DIR__ constant to check the current directory
you're in.
In almost all cases, PHP will also add the following items to the array of files/directories.
In addition, we looked at the mkdir() and rmdir() functions for creating and removing directories, respectively. Both functions accept the
directory to create/delete.
mkdir("foo");
rmdir("foo");
Before working with a file, you should always verify that it exists by using the file_exists() function, which accepts the path to a file. It'll return
a boolean on whether the file was found or not.
file_exists("example.txt");
The file size can be retrieved with the filesize() function. This function also accepts a path to the file.
filesize("example.txt");
Data can be written to a file by using the file_put_contents() function. This function accepts the name of the file and the data to insert into the
file.
Sometimes, you may find yourself reading file info from the same file multiple times. Behind the scenes, PHP caches the results of some of its
filesystem functions. If you want to clear the cache, you can use the clearstatcache() function.
Lastly, you can read the contents of a file by using the file_get_contents() function.
echo file_get_contents("example.txt");
Resources
Filesystem Functions
The first time you turn on a robot, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or
BC811.
Create a function that generates names with this format. The names must be random: they should not follow a predictable sequence. 🤖
Hints
Here are the steps you can take to complete this exercise:
1. Generate an array of characters A - Z. Check out the links below for a function to use.
2. Randomize the items in the array. Check out the links below for a function to use.
3. Generate a random number between 100 - 999. Check out the links below for a function to use.
4. Return a string with the first two items from the array and a random number.
Check out these functions for helping you curate a solution to this challenge:
Starter Code
<?php
declare(strict_types=1);
<?php
declare(strict_types=1);
return "{$letters[0]}{$letters[1]}{$number}";
}
For example:
Warning: Please do not use arrow functions. They're not supported on Udemy.
Hints
Here are the steps you can take to complete this exercise:
1. Convert the string into an array with each character as an individual value. Check out the links below for a function to use.
2. Loop through the array. Check out the links below for a function to use.
3. Calculate exponentiation with the number in the current iteration with the number of items in the array. Check out the links below for a
function to use.
4. Calculate the sum of all values in the array and check if it equals the original number. Check out the links below for a function to use.
5. Return the result as a boolean
Check out these functions to help you curate a solution to this challenge:
Starter Code
<?php
declare(strict_types=1);
<?php
declare(strict_types=1);
For example, the string "49142" has the following 3-digit series:
"491"
"914"
"142"
"4914"
"9142"
And if you ask for a 6-digit series from a 5-digit string OR if you ask for less than 1 digit, return an empty array.
Note that these series are only required to occupy adjacent positions in the input; the digits need not be numerically consecutive.
Hints
Here are the steps you can take to complete this exercise:
1. Create a conditional statement to check if length of the string is longer than the requested size or smaller than 1.
2. Return an empty array if either condition is true.
3. Otherwise, create an array to store the results.
4. Start looping through series minus the size.
5. Insert a sub-string into the results
6. Return the results
Check out these functions to help you curate a solution to this challenge:
<?php
declare(strict_types=1);
<?php
declare(strict_types=1);
$results = [];
return $results;
}
Classes
In this lecture, we learned about classes. Classes are blueprints for creating objects. An object can be anything we want. We can use an object to
represent a UI element or a database entry.
class Account {
It's standard practice to name your class after the name of your file and to use pascalcasing.
Since classes are just blueprints, we can't interact with the data inside of a class until it has been instantiated. Instantiation is the process of
creating an object from a class. We can instantiate a class using the new keyword like so.
Properties
In this lecture, we talked about properties, which are variables defined inside a class. We must define a property by using an access modifier
followed by the name of the variable.
class Account {
public $name;
public $balance;
}
An access modifier determines how accessible the property is outside of the class. The public keyword allows for a property to be updated
anywhere from our script.
We can access the property using the -> from the respective instance.
$myAccount->balance = 10;
var_dump($myAccount->balance);
If we don't assign a value to a property, the value will be uninitialized . We can set properties to any type of value, but we can't use complex
operations, such as setting a property to a function.
Resources
Access Modifiers
Magic Methods
In this lecture, we learned about magic methods. A method is the terminology for a function defined inside a class. Magic methods are functions
defined inside a class that is automatically called by PHP.
There are various magic methods available, but the most common method is __construct() . Most magic methods begin with two _ characters.
It's recommended to avoid using two _ characters in your own methods to reduce confusion.
class Account
{
public string $name;
public float $balance;
In this example, the __construct() method has the public access modifier. This allows for our method to be accessible anywhere. Whenever
we create a new instance of the Account class, the __construct() method gets called.
Resources
Magic Methods
Null-safe Operator
In this lecture, we talked about the null-safe operator, which allows us to access a property method without worrying about PHP throwing an error
because the instance does not exist. If we attempt to access a property or method on a null value, PHP throws an error.
$myAccount = null;
$myAccount?->deposit(500);
The null-safe operator will check if the value is null before accessing a property or method. If a variable is null , PHP will not bother running the
next portion of code, which prevents an error from being thrown.
Understanding Namespaces
In this lecture, we talked about namespaces. There are no notes for this lecture.
Creating a Namespace
In this lecture, we created a namespace by using the namespace keyword. Following this keyword, we must provide a name. Namespaces follow
the same naming rules for classes or functions. Generally, most developers use pascalcasing for namespaces.
namespace App;
class Account {
// Some code here...
}
We can access a class from within a namespace by typing the namespace and class like so.
However, you may not want to type out the entire path. You can use the use keyword to import a class from a namespace. This will allow you to
create an instance with just the class name.
use App\Account;
The second thing to be aware of is that we don't need to use the use statement or type out the complete namespace when classes exist in the
same namespace.
Another thing to know is that PHP will only resolve classes to the current namespace. For example, let's say we wanted to use a class called
DateTime from a file in the App namespace.
new DateTime();
PHP will throw an error because it'll attempt to look for the class with the following path: App\DateTime . In order to use a class from a different
namespace, such as the global namespace, we must add the \ character before the classname like so.
new \DateTime();
Alternatively, we can import the class with the use keyword without the \ character.
use DateTime;
new DateTime();
It's important that the use statement to be in the same file as where we're using the class. The use statement only applies to the current file.
new DT();
Resources
Autoloading Classes
In this lecture, we learned how to automate the process of loading PHP files with classes. There's a function called spl_autoload_register()
that we can call for intercepting classes that are used in a program, but haven't been defined.
We can pass in a function that will be responsible for loading a specific class, which is passed on to our function as a parameter.
spl_autoload_register(function($class) {
$formattedClass = str_replace("\\", "/", $class);
$path = "{$formattedClass}.php";
require $path;
});
In the above example, the first thing we're doing is replacing the \ character with a / character since the require statement does not allow \
characters. To accomplish this, we're using the str_replace() function, which accepts the string to search for in a given string, the string to
replace it with, and the string to search.
In the first argument, we're escaping the \ character by using \\ since the \ character escapes a string. Escaping a string is the process of
telling PHP not to close the string with the closing double-quote.
Next, we created a variable called $path , which contains the path to the file that contains the class. Lastly, we use the require keyword to load
the file.
class Account {
public const INTEREST_RATE = 2;
}
Account::INTEREST_RATE;
class Account {
public static int $count = 0;
}
Account::$count;
Alternatively, you can also access a static property from an instance of a class.
Typically, static properties are avoided since they can be altered anywhere, just like global variables. On the other hand, static methods are
popular as a means of creating utility methods. Take the following:
class Utility {
public static function printArray(array $array) {
echo '<pre>';
print_r($array);
echo '</pre>';
}
}
Since the method is static, we can invoke the method without requiring an instance like so.
Utility::printArray([1, 2, 3]);
In your classes, you can prevent outside sources from modifying properties by using the private access modifier.
By changing the modifier to private , only the current class can modify a given property.
But what if we want to read or update the property? In this case, we can create getter or setter methods, which are just methods that can be used
for accessing a specific property. Here's an example of a getter.
It's common practice to set the name of the method to the word "get" followed by the name of the property to grab. One of the main benefits of
getters is being able to format values before returning them.
As for setters, they're methods for updating a property. It's standard practice to set the name to the word "set" followed by the name of the
property.
$this->balance = $newBalance;
}
The main benefit of setters is that we can validate data before updating a property. Overall, getters and setters give us more power over how our
properties are read and set.
We're not limited to private properties. Methods can be private too. This can be useful when you want to subdivide a method into multiple actions
without exposing a method outside of a class.
$this->balance = $newBalance;
$this->sendEmail();
}
In the following example, the sendEmail() method is private, which will be responsible for sending an email to the user if the balance is
successfully updated.
This idea can be applied to programming. We can define classes to perform complex actions and then use them elsewhere without having to
understand the complete implementation details of a class's methods.
class Toaster {
public int $slots = 2;
class ToasterPremium {
public int $slots = 4;
In this example, we have two classes with the same property and method, which is redundant. To reduce the amount of code you can write, you
can use the extends keyword for a class to inherit properties and methods from another class.
class Toaster {
public int $slots = 2;
In the above example, the ToasterPremium class inherits from the Toaster class. Therefore, it's not necessary to add the toast() method again.
In addition, if we want to override properties or methods from the parent class, we're allowed to do so. PHP prioritizes the child class properties
and methods over the parent.
One thing to keep in mind is that the $this keyword points to the current instance, even if it's being used in the parent class.
Protected Modifier
In this lecture, we learned about the protected modifier. PHP allows you to override properties, but if a property is private , child classes won't be
able to change the value of a parent class. You can get the best of public and private properties by using the protected modifier.
The protected modifier allows child classes to modify parent properties but prevent code outside of a class from changing the property value.
Overriding Methods
In this lecture, we learned about overriding methods. We're allowed to override methods from parent classes. The overriding method must have
the same method signature, which shares the name and parameter list.
It's completely possible to invoke a parent's method after overriding it by using the parent keyword. For example, let's say a parent class had a
method called foo() . We can call it like so.
parent::foo();
If you ever call the original method, you should always call it first. Otherwise, you may get unexpected behavior in your classes.
Lastly, you can use the final keyword to prevent a class from being inherited.
Or, you can apply it to a method to prevent the method from being overridden.
class SomeClass {
final public function someMethod() {
}
}
By defining an abstract class, it cannot be instantiated. So, doing the following produces an error.
new Product();
Our abstract classes can have implemented methods and unimplemented methods like so.
For unimplemented methods, you can use specify them with the abstract keyword. If we extend an abstract class, the abstract method must be
implemented from the child class.
Interfaces
In this lecture, we learned how to use interfaces, which are a feature in PHP for defining how a class should be implemented. Interfaces can
contain method definitions that should be implemented by a class. We can create an interface with the interface keyword.
interface RestaurantInterface {
public function prepareFood();
}
It's common practice to add the word "Interface" to the name. Alternatively, you can begin the name with the capital letter "I." This would produce
an interface name such as IRestaurant . Either naming convention is valid.
We can apply an interface to a class by adding the implements keyword to the class. Multiple interfaces can be added to a class by separating
them with a comma.
class FoodApp {
public function __construct(RestaurantInterface $restaurant) {
$restaurant->prepareFood();
}
}
By doing so, any class that implements the RestaurantInterface interface can be passed into the __construct() method of the FoodApp class.
Let's assume we had two classes called RestaurantOne and RestaurantTwo that implement the RestaurantInterface interface. As a result, you
can pass in instances of these classes to the FoodApp class like so.
Polymorphism can also be used with abstract classes. Here are the differences between abstract classes and interfaces.
Abstract Classes
Interfaces
Anonymous Classes
In this lecture, we learned about anonymous classes, which are just classes that don't have a name. For example, let's say we wanted to pass an
anonymous class to the FoodApp class. We can do so with the following code:
Similar to regular classes, anonymous classes support interfaces, inheritance, methods, and properties. What if we want to pass on data to a
__construct() method? We can do so by adding the argument list after the class keyword like so.
$restaurant = new FoodApp(new class("popup restaurant") implements RestaurantInterface {
public function __construct(
public string $name
) {}
Docblocks
In this lecture, we learned about docblocks, which is a format for documenting your functions and classes. They're written as a multiline
comment like so.
/**
* Neatly prints an array
*
* Outputs an array surrounded with <pre> tags for formatting.
*
* @param array $array The array to output
*/
public static function printArray(array $array) {
echo '<pre>';
print_r($array);
echo '</pre>';
}
In this example, we're documenting a method called printArray() . Docblocks have a few sections. Firstly, you can provide a summary, which
should be a sentence long.
If you need to add more information, you can add a description. The summary and description should be separated by a new line. Descriptions
don't have limits as to how long they can be.
Afterward, you can start adding tags, which allow you to describe specific parts of your method. For example, you can use the @param tag to
describe your parameters. The have the following format.
Docblock is slowly losing popularity. However, it can be beneficial to know since it's prevalent in projects that use older versions of PHP. In
addition, they can be helpful for providing human-readable descriptions of your function's behavior, parameters, and return values.
Resources
Docblock
Throwing Exceptions
In this lecture, we learned how to throw an exception from PHP. We can use the throw keyword followed by a new instance of the Exception
class.
The Exception class will format your message in the output along with the filename, line number, and other pieces of information related to the
error. PHP has other classes for throwing exceptions, you can refer to the resource section for a list of them.
For example, we can use the InvalidArgumentException class for arguments that are invalid for a method call.
Why do we say throw an exception? Because it implies that an exception can be caught and can be handled for a more custom experience, such
as redirecting the user instead of completely stopping the script.
Resources
Custom Exceptions
In this lecture, we learned how to create a custom exception class instead of using PHP's exception classes. Custom exceptions can be created
to customize the errors thrown by our script. All exceptions derive from the Exception class. So, all you have to do is create a class that extends
the Exception class.
You can override any of the properties from the class. Check out the resource section for a link to the Exception class for a complete list of
properties.
Resources
Exception Class
Catching Exceptions
In this lecture, we learned how to catch an exception by using the try catch statement. If we want to catch errors, we must surround the code
with the try block.
try {
Utility::printArray([]);
}
try {
Utility::printArray([]);
} catch(EmptyArrayException|InvalidArgumentException $e) {
echo "Custom Exception: {$e->getMessage()} <br />";
} catch (Exception $e) {
echo "Default Exception: {$e->getMessage()} <br />";
}
In the catch block's parameter list, we can accept the error that was thrown. In addition, if we add the type, we can apply the catch block to
specific exceptions. Multiple exceptions can be handled by a single catch block by separating them with a | character.
If we want to handle all exceptions, we can add a catch block where the $e parameter is Exception since all exceptions derive from this class.
Lastly, we can add the finally block that will be executed regardless if an error gets thrown or not.
try {
Utility::printArray([]);
} catch(EmptyArrayException|InvalidArgumentException $e) {
echo "Custom Exception: {$e->getMessage()} <br />";
} catch (Exception $e) {
echo "Default Exception: {$e->getMessage()} <br />";
} finally {
echo "Finally block <br />";
}
var_dump($date);
It's possible to alter the timezone by passing in an instance of the DateTimeZone class as the second argument to the DateTime class. The
DateTimeZone class accepts a valid timezone, refer to the links in the resource section for a complete list of supported timezones.
The DateTime class offers various methods for manipulating the date.
setTimezone() - Change the timezone. Accepts a new instance of the DateTimeZone class.
setDate() - Changes the date. Accepts the year, month, and day.
setTime() - Changes the time. Accepts the hour and minutes.
You can output the date alone by using the format() method, which accepts a series of placeholders.
$date->format("F j Y")
Resources
DateTime Class
Date Function
Time Function
Timezones
Carbon
class CurrentWeek {
public \DateTime $date;
public int $daysFrom = 0;
By default, if we pass in an object to the foreach loop, PHP will loop through the properties. If the properties are public, they'll be retrieved.
Protected or private properties cannot be looped through.
We can customize how an object is iterated by implementing the Iterator interface in our class.
current() - This function must return a value that will be used in the current iteration of the loop.
key() - This function must return a key that will be associated with the current value.
next() - This function runs after the current iteration is finished running. We can use this opportunity to move on to the next value.
rewind() - This function is called when a new loop is started. Mainly used for resetting the values.
valid() - This function is called after the rewind() and next() functions to check if the current value is valid.
In our example, we're using the modify() method from the DateTime class. This method can be used to modify the date using a string format.
It's the same type of value you can pass into a new instance of the DateTime class.
In some cases, you may want to accept an object that can be looped through. PHP has a data type called iterable that can be used like so.
Resources
Iterator Interface
Supported Date and Time Formats
Note: You do not need to understand anything about nucleotides or DNA to complete this exercise.
DNA is a long chain of other chemicals, and the most important are the four nucleotides, adenine, cytosine, guanine, and thymine. A single DNA
chain can contain billions of these four nucleotides, and the order in which they occur is important! We call the order of these nucleotides in a bit
of DNA a "DNA sequence."
We represent a DNA sequence as an ordered collection of these four nucleotides, and a common way to do that is with a string of characters
such as "ATTACG" for a DNA sequence of 6 nucleotides. 'A' for adenine, 'C' for cytosine, 'G' for guanine, and 'T' for thymine.
Given a string representing a DNA sequence, count how many of each nucleotide is present.
For example:
Starter Code
<?php
declare(strict_types=1);
class DNA {
public function nucleotideCount(string $input): array
{
}
}
<?php
declare(strict_types=1);
class DNA {
public static function nucleotideCount(string $input): array
{
return [
'A' => substr_count($input, 'A'),
'C' => substr_count($input, 'C'),
'T' => substr_count($input, 'T'),
'G' => substr_count($input, 'G'),
];
}
}
Note that all our students only have one name. (It's a small town, what do you want?)
Note: Please don't use constructor property promotion, as it doesn't work in Udemy.
Starter Code
<?php
declare(strict_types=1);
class School
{
public function add(string $name, int $grade): void
{
}
}
<?php
declare(strict_types=1);
class School
{
private $students = [];
return $grade;
}, $this->students);
}
}
Project Overview
In this lecture, we discussed what we'll be building for the rest of this course. Two projects will be built, a custom expense-tracking application
and a framework.
What is a framework?
A framework is a set of tools and solutions for common problems. Most projects have the same requirements, from authentication to form
validation. Frameworks provide a basic foundation to save you time from repeatedly writing the same solution.
Text Editors
This lecture does not have any notes. The content is meant to be consumed via video.
Resources
Resources
XAMPP
MAMP
WAMP
Laragon
Exploring XAMPP
This lecture does not have any notes. The content is meant to be consumed via video.
We created our project in the htdocs folder called phpiggy . Within this folder, we created another folder called public . There is no official
standard project structure. PHP allows you to structure your project to how you see fit.
Typically, developers create a folder called public for files that should always be accessible to the public. Files outside this directory should be
inaccessible via URL.
Within this folder, we created a file called index.php with a simple text message.
A virtual host can be added by modifying Apache's configuration. You can find this file via the control panel by going to Config > Apache
(https.conf).
In this file, comments are written with the # symbol. Anything else is considered a directive. Directives are instructions for configuring the
settings. Apache has a directive called Include for loading additional configuration files. Here's an example that loads a configuration file for
virtual hosts.
# Virtual hosts
Include conf/extra/httpd-vhosts.conf
In the httpd-vhosts.conf , you can add a virtual host with the following configuration:
<VirtualHost *:80>
# ServerAdmin webmaster@dummy-host2.example.com
DocumentRoot "D:/xampp/htdocs/phpiggy/public"
ServerName phpiggy.local
# ErrorLog "logs/dummy-host2.example.com-error.log"
# CustomLog "logs/dummy-host2.example.com-access.log" common
</VirtualHost>
The <VirtualHost> is a container for applying directives to a specific directory in your project as opposed to applying directives to the entire
server.
In addition, we must update the hosts file to have our machine point to the Apache web server like so.
127.0.0.1 phpiggy.local
You can refer to the resource section for info on how to find the hosts file. After making those changes, you can view your site by going to:
http://phpiggy.local
Resources
Configuring PHP
In this lecture, we learned how to configure PHP. Firstly, you can view the current PHP configuration by using the phpinfo() function.
However, if you want to update the configuration settings, you can do so by updating a file called php.ini . In XAMPP, you can navigate to Config
> PHP (php.ini) to view this file.
Alternatively, you can use the ini_set() function to update a specific setting. There are two arguments, the name of the setting and the value.
ini_set("memory_limit", "255M");
You can view a specific setting by using the ini_get() function, which accepts the name of the setting.
ini_get("memory_limit");
Resources
php.ini Directives
declare(strict_types=1);
namespace Framework;
class App
{
public function run()
{
echo "App is running!";
}
}
Any code related to our framework will be placed inside the Framework namespace.
Bootstrapping an Application
In this lecture, we bootstrapped our application in a separate file so that we can load our project's configuration in any file that wanted to initialize
our app. We have a file called bootstrap.php with the following code:
declare(strict_types=1);
use Framework\App;
return $app;
From this file, we're returning the instance. We won't be calling the run() method, which is responsible for starting the application after it has
been configured.
We're executing this method from the index.php file, which is responsible for displaying the page's contents in the browser.
$app->run();
You can open the command line by searching for a program called Powershell on a Windows machine. If you're on a Mac/Linx, you can search for
a program called Terminal.
There are dozens of commands available. Luckily, it's not required to be a master of the command line. You can get away with the following
commands:
pwd - Short for Present Working Directory. This command will output the full path you're currently in.
ls - Short for List. This command will output a full list of files and folders that are in the current directory.
cd - Short for Change Directory. This command will change the current directory. You can use two dots ( .. ) to back up a directory instead
of moving into a directory.
In Visual Studio Code, you can open the command line by going to Terminal > New Terminal. By default, the command line will point to your
project directory, which can make things easier. This saves you time from moving the command line to your project.
Understanding PSR
In this lecture, we talked about PSR, which stands for PHP Standards Recommendations. PHP allows developers to format and structure their
code any way they'd like. Over the years, developers have created a set of standards known as PSR. There are various standards depending on
what you're trying to accomplish. The larger your project, the more standards you're likely to add to your project.
The PHP community has a standard called PSR-4 for setting a standard for autoloading files. We'll be implementing this standard with the help of
Composer.
Installing Composer
This lecture does not have any notes. The content is meant to be consumed via video.
Resources
Packagist
Composer
JSON was introduced as a way to store data in a separate file. It stands for JavaScript Object Notation. Heavily inspired by JavaScript but not
required to know.
JSON Supports 5 data types: strings, numbers, booleans, arrays, and objects. Here's an example of some JSON code.
{
"name": "John",
"age": 20,
"isHungry": true,
"hobbies": ["hockey", "baksetball"],
"job": {
"company": "Udemy"
}
}
We learned about a new data type called objects. The object data type allows us to group pieces of information together. A similar concept to
array. The main difference is that objects follow a key-value format, whereas arrays can just contain values.
Resources
JSON Playground
Initializing Composer
In this lecture, we learned how to initialize composer in our project. Before we can use Composer, we must have a configuration file. Firstly, we
can view a complete list of commands available in composer by running the composer command. You can view a list of options available for a
command by adding the --help flag option like so.
A configuration file can be created manually, but it's recommended to use the composer init command to prevent typos. The following
questions may be asked:
Resources
composer.json Schema
Generating Autoload Files
In this lecture, we generated the files to help us autoload our classes in our project. First, we must configure Composer to the namespaces and
directories where the class files can be found. We must add the autoload object to configure these settings. Next, we must add the psr-4
object. There are different standards available for autoloading files, which are supported by Composer. Therefore, we must specify the standard
we'd like to use.
{
"autoload": {
"psr-4": {
"Framework\\": "src/Framework",
"App\\": "src/App/"
}
}
}
Within this object, we can add a list of properties. The property name must contain the namespace. The value must contain the directory where
the namespace is located.
composer dump-autoload
After running this command, a new directory called vendor becomes available. This is where Composer stores its generated files. If you look
inside the vendor/composer/autoloader-psr4.php file, you'll find an array of our namespaces, which tells Composer where to find our class files
with these namespaces.
Now, if we use any of our classes from the App or Framework namespaces, they'll automatically be included in our project.
What is Git?
In this lecture, we got a brief introduction to Git and GitHub. What is Git? Git a version control system for recording changes to our codebase and
maintaining a history of it. In addition, it makes collaboration easy by syncing projects across your team. There are various programs available for
tracking the history of your code. Git is considered the most popular solution. Over 90% of devs use it.
So, what about GitHub? Git runs on your machine, but you may want to publish your code online. There are various platforms that have integration
with Git. The most popular platform is GitHub.
Up until this point, programming has been pretty easy. Things are about to ramp up. If you run into problems with the course, you can ask for help
in the Q&A section. I may ask you to see your code. Udemy doesn't have the best tools for sharing code, so GitHub is the preferred way of sharing
your work for me to check.
Using GitHub
This lecture does not have any notes. The content is meant to be consumed via video.
Resources
GitHub Desktop
Git and GitHub Tutorial
Up next, we have the .gitignore file. This file contains a list of files that shouldn't be committed to your repo. It's recommended to not include
the vendor folder since the dependencies can be reinstalled with the composer install command. It also keeps your project file size small.
composer.phar
/vendor/
Resources
Git Attributes
PHPiggy GitHub Repository
Understanding Routing
This lecture does not have any notes. The content is meant to be consumed via video.
Firstly, we're using the Directory to apply these settings to our project's directory. Next, we're enabling the module by using RewriteEngine On .
Afterward, we check if the request is for a specific file or directory using the RewriteCond directive. Apache creates a variable called %
{REQUEST_FILENAME} that contains the file being requested.
Lastly, if both conditions pass, we're using the RewriteRule directive to accept any request and let the index.php file handle it instead of
allowing Apache to send back the file. The [L] flag prevents any other additional rewrite rules from applying to our request as an extra
precaution.
In the index.php file, we printed the superglobal variable $_SERVER . This variable contains information on the server, including the requested URI
under a key called request_uri .
echo "<pre>";
print_r($_SERVER);
echo "</pre>";
Resources
Behind the scenes, XAMPP is configured to reject access to the .htaccess file is accessed directly. If you're not using XAMPP, you can use the
following configuration to prevent access.
#
# The following lines prevent .htaccess and .htpasswd files from being
# viewed by Web clients.
#
<Files ".ht*">
Require all denied
</Files>
If you don't plan on using the .htaccess file, you should disable it by using the AllowOverride directive.
AllowOverride none
Resources
htaccess File
Sugar Functions
In this lecture, we talked about sugar functions, which are just functions to act as a shortcut for specific actions, such as outputting a variable's
contents with <pre> tags. Here's an example of a function we defined for dumping a variable's contents.
function dd($value)
{
echo '<pre>';
var_dump($value);
echo '</pre>';
die();
}
Adding Routes
In this lecture, we updated our Router class to store routes. A route refers to the path to visit a page. It’s the portion of the URL after the domain.
In our class, we have a property called $routes to store the routes. Next, we defined a method called add() to register new routes. New routes
are associative arrays with the path.
class Router
{
private array $routes = [];
In addition, we had to update the App class to have the same method since the $router property is private.
Resources
HTTP Methods
$this->routes[] = [
'path' => $path,
'method' => strtoupper($method)
];
Before inserting the $method parameter, we're passing it through the strtoupper() function. This function is defined by PHP. It'll transform the
characters in a string into all uppercase letters.
Normalizing Paths
In this lecture, we talked about normalizing paths. During route registration, developer may format their paths differently. For example, let's say we
a route for a list of team members. Possible paths could include:
about/team
/about/team
/about/team/
Inconsistencies can lead to issues. Normalizing is the process of standardizing a value by having consistent formatting. We decided to define a
method to normalize our paths. All paths will begin and end with a / .
In this method, we're accepting the $path and returning the formatted version. First, we're removing / character from the beginning and end of a
string with the trim() function. Next, we appended / characters to the path before returning it.
return $path;
}
In the add() method, we updated the $path variable before updating the $routes array.
$path = $this->normalizePath($path);
In this expression, we're checking for the / character. A list of characters can be written inside the [] . The {2,} portion of the expression
allows to check if they are two or more / characters. Otherwise, we can leave the / character alone before replacing it.
Resources
String Functions
Regex101
Expressions must start and end with the # character to denote the beginning and end of the expression. The return value of this function will be
the string with the replaced values.
Resources
PCRE Functions
Creating a Controller
In this lecture, we created a controller, which is a separate file with a class definition responsible for loading a specific page. We created the
following directories: src/App/Controllers . Inside this newly created directory, we created a file called HomeController.php with the following
code:
declare(strict_types=1);
namespace App\Controllers;
class HomeController
{
public function home()
{
echo 'Homepage';
}
}
In this example, we're adding the App\Controllers namespace to contain the HomeController class. Most of the logic should be familiar to you
as all we're doing is creating a method called home for rendering the contents of the home page.
Registering Controllers
After creating this controller, we updated the bootstrap.php file to use this controller when the user visits the / homepage.
In this example, we're passing in the class and method names as strings. It's more efficient to allow the router to instantiate the class instead of
passing in a direct instance. This is more efficient as you won't need an instance for every controller unless the user is visiting the corresponding
route.
Next, we updated our App class to accept this information. All we're doing is passing on the controller to the router to store it with the route.
$this->routes[] = [
'path' => $path,
'method' => strtoupper($method),
'controller' => $controller
];
}
use App\Controllers\HomeController;
Dispatching a Route
In this lecture, we dispatched a route. Dispatching describes the process of sending something off to a location. In our case, we're sending
content to the browser based on the route. We defined a method called dispatch() , which accepts a path and method. In the method, we're
formatting the path and method to be consistent with the values in our routes.
Lastly, in the run() method of the App class, we called the dispatch() method.
$this->router->dispatch($path, $method);
}
We're grabbing the path from the $_SERVER superglobal variable. However, this variable stores the full URL. We're only interested in the path. To
grab the path only, we passed the value into the parse_url() function. This function extracts information on the URL. Since we're only interested
in the path, we passed in the PHP_URL_PATH constant to instruct the function to only return the path.
As for the method, we just simplified the name to $name instead of $_SERVER['REQUEST_METHOD'] . Lastly, we called the dispatch() method with
these variables.
In the example above, we're using a regular expression. We're performing an exact match with this expression. The ^ character in the expression
makes sure the path begins with the value. The $ character in the expression makes sure the path ends with the value.
Afterward, we created an instance of the class with the new keyword. It's completely acceptable to provide a string containing the class name for
this keyword. PHP is more than capable of resolving the class. The instance gets stored in a variable called $controllerInstance .
$controllerInstance->{$func}();
Resources
PSR-12
1. Easier for frontend developers to understand your templates since the syntax is easier to read than pure PHP.
2. Template engines offer a layer of security to prevent malicious data from being outputted onto the page.
Twig by Symfony and Blade by Laravel is the most popular libraries for adding a template engine to your app. For our app, we decided to create a
custom template engine.
Next, we defined a class to store paths to various areas in our app called Paths .
namespace App\Config;
class Paths
{
public const VIEWS = __DIR__ . "/../views";
// public const ROOT = __DIR__ . "/../../../";
// public const STORAGE_CACHE = __DIR__ . "/../../../storage/cache";
// public const ENTITY = __DIR__ . "/../Entity";
// public const SOURCE = __DIR__ . "/../../";
// public const STORAGE_UPLOADS = __DIR__ . "/../../../storage/uploads";
}
Rendering a Template
In this lecture, we rendered the template using the TemplateEngine class from our controller. First, we defined a method called render()
method, which accepts the template name. Inside this method, we included the template by using the include keyword. The path to a template
is the $basePath property with the $template parameter.
public function render(string $template)
{
include "{$this->basePath}/{$template}";
}
$this->view->render("index.php");
Extracting Arrays
In this lecture, we updated our engine to display data. First, we updated the render method's signature by accepting an array of data.
By default, the method's parameters are accessible to templates. However, they must be accessed through the $data variable. For convenience,
you may want to extract each item in the array into a separate variable. You can do so with the extract method. This method accepts the
associative array to extract and how it should handle existing variables.
extract($data, EXTR_SKIP);
In this example, we're extracting the $data variable and using the EXTR_SKIP constant to tell PHP not to overwrite existing variables.
Resources
extract() function
We took a look at adding support for output buffers if a hosting provider disables this feature. Ultimately, hosting providers have the final say. You
can message your hosting provider to enable output buffering if they disable it. In most cases, output buffering is enabled since its part of the
core.
Output buffering can be enabled by editing the php.ini file. You can add the following value to enable output buffering:
output_buffering = On
Alternatively, to enable output buffering and limit the buffer to a specific size, use a numeric value instead of on . For example, to set the
maximum size of the output buffer to 4096 bytes, modify the output_buffering directive in the php.ini file as follows:
output_buffering = 4096
To disable output buffering, modify the output_buffering directive in the php.ini file as follows:
output_buffering = off
Resources
ob_start();
In our render() method, we used output buffers to store the HTML without it being sent to the browser.
public function render(string $template, array $data = [])
{
extract($data, EXTR_SKIP);
ob_start();
include $this->resolve($template);
$output = ob_get_contents();
ob_end_clean();
return $output;
}
Most importantly, we retrieved the content from the buffer by using a function called ob_get_contents() . This function returns the content as a
string, which we store in a variable called $output . Afterward, we cleaned and closed the output buffer with the ob_end_clean() function. This
step is very important. Otherwise, the content may get sent too early.
Lastly, we returned the $content variable. By using output buffers, we can perform additional actions on the template after it has been retrieved.
Loading Assets
In this lecture, we loaded our site's CSS by creating a file in the public/assets directory called main.css . You can refer to the resource section of
this lecture for the CSS. It's important that the CSS is added to the public directory. Otherwise, you won't be able to access the file through HTTP.
Resources
Adding Partials
In this lecture, we learned how to split our template into separate files by using partials. Partials are not entire templates. They're meant to
contain portions of a larger template like headers, footers, and sidebars.
It's common practice to store partials in a folder called partials . Not required but recommended. Next, a common naming convention for partial
template files is to prefix the name with an _ character. For example, if we had a partial for the header, the filename would be _header.php .
In our engine, we decided to create a method to help us easily generate an absolute path from our templates called resolve() . It accepts the
path and returns the path with the base path from the instance.
After creating a partial, we can load the partial with the include tag like so.
Resources
About Template
Autoloading Functions
In this lecture, we learned how to autoload functions. Functions can be imported into a file by using use function . Here's an example of how we
imported functions for registering routes.
Since there is no standard for auto importing functions, we must manually instruct Composer to autoload the function by adding an array called
files to the autoload option in the composer.json file. This array must contain a list of files to load.
{
"autoload": {
"psr-4": {
"Framework\\": "src/Framework",
"App\\": "src/App/"
},
"files": ["src/App/Config/Routes.php"]
}
}
Lastly, you must update your autoload files using the composer dump-autoload command.
Creating a Container
In this lecture, we created a container, which is an object that contains instructions for creating instances of our classes. Here's what our class
looks like.
class Container
{
}
declare(strict_types=1);
return [
];
This file returns an array. Definitions refer to the list of dependencies available in our application. Next, we added a method called
addDefinitions() in the Container class to accept the array of definitions.
Afterward, we updated the App class __construct method to accept a path to the file with the container definitions. In this example, we're
checking if a path was provided. If it was, we'll proceed to include the file and pass on the array to the container through the addDefinitions
method.
if ($containerDefinitionsPath) {
$containerDefinitions = include $containerDefinitionsPath;
$this->container->addDefinitions($containerDefinitions);
}
}
Lastly, we updated our Paths class to contain the path to the src directory.
class Paths
{
public const VIEWS = __DIR__ . "/../views";
public const SOURCE = __DIR__ . "/../../";
}
use Framework\TemplateEngine;
use App\Config\Paths;
return [
TemplateEngine::class => fn () => new TemplateEngine(Paths::VIEWS)
];
It's common practice to use the class name as the name for our definition. The value is a function that returns the instance, but why a function?
The factory design pattern is a pattern where a function handles the process of creating an instance. The main benefit of this pattern is that we
don't have to create an instance immediately. We can wait until the instance is needed before creating it, thus saving memory.
Merging Arrays
In this lecture, we learned how to merge two arrays. We're merging the definitions stored in the container with the definitions passed into the
addDefinitions method like so.
The array_merge() function accepts an unlimited number of arrays and returns a merged array. An alternative syntax is to use the ... operator
like so.
The main difference is that the spread operator is faster than the array_merge() function, but it is less readable. Other than that, the behavior is
the exact same.
Reflective Programming
In this lecture, we learned about reflective programming. Most programming languages allow you to inspect the code of your application. This is
referred to as reflection. PHP supports reflection. We're using the reflection API so that we can inspect our classes for dependencies.
A class can be inspected by creating a new instance of the ReflectClass class. This class accepts the name of the class to inspect.
dd($reflectionClass);
}
Validating Classes
In this lecture, we validated the class given to the ReflectionClass . Sometimes, a container may receive an abstract class, which cannot be
instantiated. In this case, we should check that a class can be instantiated. If it can't, we should throw a custom ContainerException like so.
if (!$reflectionClass->isInstantiable()) {
throw new ContainerException("Class {$classname} is not instantiable");
}
Resources
ReflectionClass
Validating the Constructor Method
In this lecture, we validated the __construct() method of our classes. In some cases, a class may not have this method defined. If not, there isn't
a point in checking for dependencies since this method contains the list of dependencies. First, we updated the __construct() method in the
HomeController class by moving the $view property to the parameter list like so.
Next, we grabbed the construct method with the getConstructor() method. This method returns null if the method doesn't exist. We can
proceed to use a conditional statement to check for a value. If it's null , the instance of the class is returned since we don't have to check for
dependencies.
$constructor = $reflectionClass->getConstructor();
if (!$constructor) {
return new $classname;
}
If there aren't any parameters, the array will be empty. We used the count() function to count the items in the array. If there aren't any items, we
don't have to check for dependencies. We can proceed to instantiate the class and return the instance.
$parameters = $constructor->getParameters();
if (count($parameters) === 0) {
return new $classname;
}
Resources
ReflectionMethod
Validating Parameters
In this lecture, we validated the parameters in the __construct() method of a given controller. First, we looped through them.
};
Next, we grabbed the name of the data type of the parameter. The name will be necessary for error messages in case the parameters don't pass
validation. The type will be important for checking if the data type is built in or exists.
$name = $param->getName();
$type = $param->getType();
The first validation we performed was checking if a type hint was given to a parameter. If not, then we threw an error.
if (!$type) {
throw new ContainerException(
"Failed to resolve class {$classname} because param {$name} is missing a type hint"
);
}
Lastly, we checked if the type is an instance of the ReflectionNamedType class. This class is given to a parameter when a type hint is given to a
class and is not a union type or intersection type. Lastly, we called the isBuiltIn() method to verify we're not given a primitive type like string
or bool . If either of these conditions fails, we won't be able to instantiate a dependency for our controller.
$factory = $this->definitions[$id];
$dependency = $factory(); // $factory($this);
return $dependency;
}
In this method, we validate the ID by checking if it exists within the array. If it doesn't, we'll throw an exception.
Afterward, we grab the factory function, invoke it, and return the result.
Lastly, we can store the instance in the $dependencies array like so.
$dependencies[] = $this->get($type->getName());
return $reflectionClass->newInstanceArgs($dependencies);
Understanding Middleware
There are no notes for this lecture.
Adding Middleware
There are no notes for this lecture.
Creating Middleware
In this lecture, we created our first middleware called TemplateDataMiddleware . It's common practice to add the word "Middleware" to the class
to help other developers identify middleware. For middleware, we created a dedicated namespace called App\Middleware .
Lastly, we registered this class through the Middleware file like so.
$app->addMiddleware(TemplateDataMiddleware::class);
We're passing in the class name since we want to be able to resolve dependencies. It's better to let the router resolve dependencies instead of
instantiating the class ourselves.
Interface Contracts
In this lecture, we talked about contracts. Interfaces are sometimes referred to as contracts since they force a class to have a specific
implementation. We decided to define a contract for middleware for consistency. It's called MiddlewareInterface .
namespace Framework\Contracts;
interface MiddlewareInterface
{
public function process(callable $next);
}
use Framework\Contracts\MiddlewareInterface;
$action = function () {
echo "Main Content <br>";
};
$action();
In some cases, we may have an array of functions we'd like to execute before executing the main function. Here's an example of an array of
functions.
$funcs = [
function ($next) {
echo "a <br>";
$next();
},
function ($next) {
echo "b <br>";
$next();
},
function ($next) {
echo "c <br>";
$next();
}
];
In each function, we're going to accept the next function to run. This allows us to run code before and after the next function.
foreach($funcs as $func) {
$action = fn () => $func($action);
}
We're overriding the previous function but passing it onto the next function so that it isn't lost forever. By doing so, we'll still be able to invoke the
previous function. This is how middleware is implemented in most cases. In the next lecture, we'll apply the same logic to our framework.
Next, we looped through the $middlewares property, instantiated the middleware, and then overridden the $action variable with the middleware
while passing on the previous function to the current action.
Lastly, we invoked the $action function, which starts the chain of functions.
$action();
$middlewareInstance = $container ?
$container->resolve($middleware) :
new $middleware;
Next, we extracted this property inside the render() method. It's important to extract the data after the data from the controller. Otherwise,
global data may get prioritized over the component's data.
extract($data, EXTR_SKIP);
extract($this->globalTemplateData, EXTR_SKIP);
Lastly, we defined a method called addGlobal() for inserting new data into our engine.
The TemplateDataMiddleware class was updated to call this method to insert a default title in case a controller does not provide one.
$next();
}
}
Singleton Pattern
In this lecture, we talked about the issue with our middleware. At the moment, our template does not use the global data from the middleware.
This is because our middleware and controllers receive unique instances of the global data. Therefore, they won't have access to the same data.
To solve this issue, we added the singleton pattern to the container. If an instance exists for a dependency, we'll return the existing instance
instead of creating a new one.
In the Container class, we created a property for storing existing instances called $resolved .
Next, we stored the instance of a dependency in the array after it had been created from the get() method.
$this->resolved[$id] = $dependency;
Lastly, we checked if an instance exists by using the array_key_exists() function. If so, we'll return the instance. This process was performed
from within the get() method before an instance of the dependency was created. It's important to return the instance. Otherwise, the instance
will still get created.
if (array_key_exists($id, $this->resolved)) {
return $this->resolved[$id];
}
Resources
Singleton Pattern
Resources
Register Template
The action property can point to a specific URL, whether it's absolute or relative. This attribute is optional. If not supplied, the form gets
submitted to the same page. The method attribute allows us to set an HTTP method. Only GET and POST are supported.
Next, form data is only sent if the input elements have the name attribute like so:
Resources
var_dump($_POST);
Understanding Services
In this lecture, we talked about how services can keep our controllers thin. It's considered good practice for controllers to have as little logic as
possible. They should be responsible for receiving requests and returning a response. The job of a service is to handle other logic from validation
to processing transactions.
We created a class called ValidatorService , registered it with our container, and injected it into the AuthController class.
Typically, classes in your framework should know the "how" whereas classes in your application should know the "what." This is a general
guideline that you won't always be able to follow, but it is a good place to start when deciding where to place logic in your project.
interface RuleInterface
{
public function validate(array $data, string $field, array $params);
public function getMessage(array $data, string $field, array $params);
}
In this example, we have an interface that requires two methods. The validate() method will be responsible for validating the value. The
getMessage() method will be responsible for generating an error message when validation fails. Both methods will accept the entire form data,
the field to validate, and an extra set of parameters.
Registering a Rule
In this lecture, we created a class requiring a field to have a value. In the validate() method, we're using the empty() function to check if the
field has a value. If it doesn't, validation fails. In the getMessage() method, we're returning a generic error message.
use Framework\Contracts\RuleInterface;
Next, we updated the Validator class to loop through this array of fields. From within the first loop, we're performing another loop to iterate
through the rules applied to a field. The array of rules is aliases. We're grabbing the instance of our rule with the alias. After doing so, we called
the rule's respective validate() method.
If an error doesn't get produced, the class moves on to the next rule.
echo "error";
}
}
$errors[$fieldName][] = $ruleValidator->getMessage(
$data,
$fieldName,
[]
);
if (count($errors)) {
dd($errors);
}
use RuntimeException;
Resources
RuntimeException Class
1xx - Information
2xx - Success
3xx - Redirection
4xx - Client error
5xx - Server error
We decided to add this code to our exception class by updating the $code property through the __construct() method like so:
We're calling the parent constructor method so that the properties are applied to the class.
Resources
Custom Middleware
In this lecture, we created custom middleware. First, we must import the appropriate interfaces to help us build the middleware.
use Framework\Contracts\MiddlewareInterface;
use Framework\Exceptions\ValidationException;
This interface forces our class to implement a method called process() . This method calls the $next() function to pass on the request to the
next middleware.
Lastly, we must register our middleware by calling the addMiddleware() method on the app instance. This method accepts an instance of our
middleware as an argument.
$app->addMiddleware(ValidationExceptionMiddleware::class);
After registering the middleware, it'll always run for every page.
Next, called the header() method allows us to add a header to the response. In this example, we're adding the Location header to change the
URL to redirect the user. Lastly, we're setting the status code to 302 to inform the browser the response should redirect the user.
try {
$next();
} catch (ValidationException $e) {
redirectTo("Location: /register");
}
Resources
Headers
Lastly, we can accept the errors as an argument to our __construct() method and use constructor property promotion to store the errors as a
property to the class.
HTTP Referrer
In this lecture, we updated our middleware to redirect the user to the page with the form if their was an error with validating their input. First, we
must grab an array of server information by using the $_SERVER variable.
One of the items in the array is called HTTP_REFERER . This item contains the URL that initiated the request. In our case, it would be the form.
$referer = $_SERVER['HTTP_REFERER'];
By using this variable, we can redirect the user back to the form page like so:
redirectTo($referer);
Resources
$_SERVER Variable
HTTP Referer
Understanding Sessions
In this lecture, we learned about sessions. Sessions are variables that continue to hold their value after a response has been sent. We can create
sessions by using the superglobal variable $_SESSION . If we add values to this array, they'll be persisted. Here's an example:
$_SESSION['errors'] = $e->errors;
Enabling Sessions
In this lecture, we learned how to enable a session. For this task, we created a middleware, which we registered like so:
$app->addMiddleware(SessionMiddleware::class);
session_start();
Just like that, PHP will keep track of the session. You can verify a session has been started by checking the cookies in your browser. A cookie
called PHPSESSID will be available with the ID of the session.
Firstly, we don't want to start a session if one is already active. We can use the session_status() method to detect an active session.
Secondly, we should check if headers were already sent. If they were, a session cannot be started. We can use the headers_sent() function to
help us detect if headers were sent.
if (headers_sent()) {
throw new SessionException(
"Headers already sent."
);
}
In the next lecture, we talked about why it's important to check for headers sent.
There are two solutions for avoiding this error. Firstly, you can call the session_start() function before you output any content. Secondly, you
can enable output buffering, which can be done through PHP's configuration.
We decided to improve our exception by creating a variable called $filename and $line . These variables can be passed into the
headers_sent() function, which will update them with the filename and line number where content was already outputted. We then used these
variables in our error message like so:
if (headers_sent($filename, $line)) {
throw new SessionException(
"Headers already sent. Consider enabling output buffering. Data outputted from {$filename} - Line: {$line}"
);
}
Resources
session_write_close();
First, we called the method for adding global data to a method, which is the addGlobal() method. This method accepts the name of the variable
and the array.
Lastly, we can view the errors by dumping them with the dd() function.
Flashing Errors
In this lecture, we decided to flash the errors. Errors shouldn't appear anymore after they've been rendered on the template. It's recommended to
destroy a variable by calling the unset() function. It accepts the variable to destroy. We can use it like so:
unset($_SESSION['errors']);
Displaying Errors
In this lecture, we rendered the errors by checking for an existence of a variable using the if statement and array_key_exists() function. Next,
we rendered an error with a <div> tag.
You can use a loop to display all errors if you would like. We decided to keep it simple by displaying the first error in the array.
Validating Emails
In this lecture, we validated the email by using the filter_var() function. This function is defined by PHP, which can be used for sanitizing or
validating values. There are two arguments, which are the value to validate/sanitize and the filter to apply.
In this example, we're applying the FILTER_VALIDATE_EMAIL filter to validate the value as an email.
Resources
filter_var() Function
Our class should be able to extract the rule alias and list of parameters. First, we created an empty array to store the parameters.
$ruleParams = [];
Next, we used the str_contains() function to check if the rule contains the : character. If it does, we should extract the parameters.
if (str_contains($rule, ':')) {
We can do so by using the explode() function and destructuring the results. The first item in the array will be the rule alias, and the second item
will be the parameters.
Lastly, we converted the parameters into an array by using the explode() function again.
if (empty($params[0])) {
throw new InvalidArgumentException('Minimum length not specified');
}
Otherwise, we'll proceed to validate the value. First, we typecasted the parameter into an integer and then compared it with the field value.
In Validation Rule
In this lecture, we created a rule for checking if a value is inside an array of values. The array of values can be passed in as a parameter. In our
ValidatorService class, we used the rule for validating a value against an array of countries.
in:USA,Canada,Mexico
In our InRule class, we used the in_array() function, which accepts the value to find in an array.
in_array($data[$field], $params);
filter_var($data[$field], FILTER_VALIDATE_URL);
$fieldOne = $data[$field];
$fieldTwo = $data[$params[0]];
Afterward, we compared both field values and returned the result from the validate() method of our rule.
Prefilling a Form
In this lecture, we prefilled a form with a user's previously submitted data for a better user experience. First, we created a middleware similar to
the validation error middleware to store the form data in a session. After using this middleware, we updated our template to use the old form
data.
For <input> elements, you can set the value attribute like so:
For <select> elements, you can use the selected attribute on <option> elements to select a specific option.
<select
name="country"
class="block w-full mt-1 rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:rin
>
<option value="USA">USA</option>
<option value="Canada" <?php echo ($oldFormData['country'] ?? '') == "Canada" ? "selected" : ""; ?>>Canada</option>
<option value="Mexico" <?php echo ($oldFormData['country'] ?? '') == "Canada" ? "selected" : ""; ?>>Mexico</option>
</select>
Lastly, for checkboxes, you can add the checked attribute to an <input> element like so.
<input
<?php echo $oldFormData['tos'] ?? false ? "checked" : ""; ?>
/>
$oldFormData = $request->getParsedBody();
$excludedFields = ['password', 'confirmPassword'];
Next, we used the array_diff_key() function to merge two arrays where identical keys are filtered out of the result. For the $excludedFields
variable, we flipped the key and values by using the array_flip() function.
$filteredFormData = array_diff_key($oldFormData, array_flip($excludedFields));
$_SESSION['oldFormData'] = $filteredFormData;
Introduction to SQL
This lecture does not have any notes. The content is meant to be consumed via video.
Resources
MySQL
MariaDB
MySQL Workbench
HeidiSQL
Sequel Pro
Creating a Database
In this lecture, we learned how to create a database with the help of PHPMyAdmin. A single server is allowed to have multiple databases. In the
tool, we created a database called phpiggy , which matches the name of our project. Not required but recommended.
Secondly, we configured the collation. The collation is the character set supported by the database. If you're interested in storing various
characters, you should use the utf8mb4_unicode_ci . This collation supports a wide range of characters from various languages.
Creating Tables
In this lecture, we created a table with a custom query. We typed the following query:
A couple of things worth noting about this query. Firstly, reserved keywords are not case-sensitive. We can write CREATE TABLE as Create Table
or create table . Either formatting works. Most developers prefer to use all uppercase letters for reserved keywords. Lowercase letters for
custom names.
The CREATE TABLE keyword will create a table. This keyword is followed by the name of the table and the columns. Inside the parentheses, we're
creating two columns called ID and name . They're separated with a comma.
After specifying a column name, we must add the data type. SQL languages offer various data types for the same type of data to allow us to
constrain the size. It's considered good practice to only specify a size that doesn't exceed your needs. You can refer to the resource section of
this lecture for a complete list of data types.
Lastly, we're using the NOT NULL keywords to prevent the ID column from accepting an empty value. Behind the scenes, MariaDB/MySQL
performs validation on your data. If a value does not match the data type of a column or is empty, the value will be rejected.
Resources
Inserting Data
In this lecture, we talked about inserting data into a database. There are common operations you'll perform when interacting with a database
called CRUD, which is short for Create, Retrieve, Update, and Delete. We explored each operation in this course.
We started with creating data. Data can be created with the INSERT INTO keywords. This is followed by the name of the table to insert data. Next,
we can specify values for each column in the table by using the VALUES keyword like so:
INSERT INTO products
VALUES (1, "Hats");
We must provide a list of values for each column in the table. Alternatively, we can provide a list of columns to add values for by adding a pair of
() after the name of the table like so:
However, this will not work if you forget to add a column that does not allow null values.
Reading Data
In this lecture, we talked about reading data from the database by using the SELECT keyword. After this keyword, we can provide a list of columns
to retrieve values from. Next, we can specify the table to grab data from using the FROM keyword like so:
SELECT ID
FROM products
We can select all columns by replacing the names of columns with a * character like so:
SELECT *
FROM products
In some cases, you may want to filter the records from a table using the WHERE keyword like so
SELECT *
FROM products
WHERE name="Hats"
You can find a complete list of comparison operators in the resource section of this lecture.
Resources
SQL Operators
Updating Data
In this lecture, we got started with updating data by using the UPDATE keyword. We can specify the table to update by adding the name of the
table afterward. Lastly, we can add the SET keyword to start updating values from specific columns.
UPDATE products
SET name="Shirts"
WHERE ID=2
It's very important to add the WHERE keyword to update specific records. Otherwise, all records will be updated.
Deleting Data
In this lecture, we deleted data from a table by using the DELETE FROM keywords. We can specify the name of the table after these keywords. By
itself, this query will delete all records. It's very important to add a condition to prevent a disaster from occurring by using the WHERE keyword.
Lastly, we deleted the table by using the DROP TABLE keyword like so:
Using PHPMyAdmin
In this lecture, we learned how to use PHPMyAdmin for creating tables, inserting data, reading data, updating data, and deleting data. We also
learned about engines in SQL databases.
An engine is responsible for processing queries. Each engine will perform certain actions better than others. The most common engine is
InnoDB . This is the default engine provided by the database, which is an all-around general-purpose engine that will cover most use cases.
There are two extensions for interacting with databases called mysqli and pdo . The mysqli extension is only compatible with MySQL, whereas
the pdo extension is compatible with multiple SQL databases. Most developers prefer the pdo extension since you can switch to a different
database with very few changes to your codebase.
You can verify the PDO extension is installed by using the phpinfo() function. If the pdo_mysql extension is listed under the list of drivers, you
should be good to.
Otherwise, you must update your environment's php.ini file. This line of code must be uncommented in the configuration file:
extension=pdo_mysql
Afterward, you must restart the server for the changes to take effect and verify that the extension is listed from the page generated by the
phpinfo() function.
Resources
PECL
{
"scripts": {
"phpiggy": "php cli.php"
}
}
In this example, we're creating a custom command called phpiggy that runs the php command to execute a PHP script. The php command
accepts a filename. We can run this command with composer by using the composer run-script phpiggy command.
Understanding DSN
Databases have a standardized format called DSN, which is short data source name. DSNs contain the connectivity details to a database. The
format is the driver, followed by the host and database name.
driver:host=value;dbname=value
Resources
Connecting to a Database
In this lecture, we learned how to connect to a database using PDO. The following information is required:
Driver - A driver for communicating with a specific database since PDO supports multiple database.
Host - The URL or IP to where the database is hosted.
Database Name - The name of the database since multiple databases can be hosted on a single server.
Username - The username of an account in a database.
Password - The password associated with the username.
$driver = 'mysql';
$host = 'localhost';
$database = 'phpiggy';
$dsn = "{$driver}:host={$host};dbname={$database}";
$username = 'root';
$password = '';
We can connect to a database with PHP by creating a new instance of the PDO class, which accepts the DSN, username, and password.
To avoid our database credentials from being outputted onto a page, we can catch the exception. PDO throws an exception called PDOException .
Here's an example of how to catch that exception.
try {
$db = new PDO($dsn, $username, $password);
} catch (\PDOException $e) {
die("Unable to connect to database.");
}
namespace Framework;
class Database
{
public PDO $connection;
try {
$this->connection = new PDO($dsn, $username, $password);
} catch (PDOException $e) {
die("Unable to connect to database.");
}
}
}
$stmt = $db->connection->query($query);
echo "<pre>";
var_dump($stmt->fetchAll());
echo "</pre>";
We can view the results by calling the fetchAll() method. If our query selects multiple records, all records will be presented on the page.
Fetch Modes
In this lecture, we learned how to modify the results. By default, PHP uses a mode called PHP::FETCH_BOTH , which returns the results in an array
with numeric and named keys. We can modify the mode by passing in the mode into the query() or fetchAll() method like so.
$db->connection->query($query, PDO::FETCH_BOTH);
$stmt->fetchAll(PDO::FETCH_BOTH);
There are dozens of modes available, but here are the most popular ones.
PDO::FETCH_NUM - Specifies that the fetch method shall return each row as an array indexed by column number as returned in the
corresponding result set, starting at column 0.
PDO::FETCH_ASSOC - Specifies that the fetch method shall return each row as an array indexed by column name as returned in the
corresponding result set. If the result set contains multiple columns with the same name, PDO::FETCH_ASSOC returns only a single value per
column name.
PDO::FETCH_OBJ - Specifies that the fetch method shall return each row as an object with property names that correspond to the column
names returned in the result set.
SQL Injections
In this lecture, we learned how our current solution leaves us vulnerable to an SQL injection. It's not uncommon to allow user input in a query. One
solution to allow user input is to use query strings. Let's say we had the following query:
In this example, we are using the $search variable to filter the results. Currently, we're not sanitizing the value, so its completely possible to use
the following value to get a complete list of results:
In this example, we're adding an OR keyword to the query to add an additional conditional statement to our WHERE portion of the query. If 1
equals 1 , SQL will return all records in a table instead of a specific record. We're also commenting the last " character by using the --
characters.
The query can be modified to drop tables, or even worse, modify a user's passwords. We can avoid these scenarios by using prepared
statements, which will be covered in the next lecture.
Resources
Superglobals
Prepared Statements
In this lecture, we learned how to avoid SQL injections by using prepared statements. A prepared statement is a query with placeholders. SQL can
replace these placeholders with values while validating the values to prevent injections.
The first step is to update our query by replacing the value with a placeholder. We can use the ? character as the placeholder.
$stmt = $db->connection->prepare($query);
Unlike the query() method, the prepare() method does not immediately execute the query. We must do so from the execute() method, which
accepts an array of values to replace the placeholders. The first placeholder gets replaced with the first item in the array, the second placeholder
gets replaced with the second item in the array, and so on and so forth.
$stmt->execute([$search]);
We can also use named parameters, which always start with the : character.
In the execute() method, we must use an associative array to map the values to their placeholders.
Alternatively, we can bind the values with the bindValue() method, which accepts the parameter name, value, and data type (optional).
$stmt = $db->connection->prepare($query);
Not all database engines support transactions. If you're using InnoDB , transactions are supported. It's always good to check beforehand.
Resources
$db->connection->beginTransaction();
Once you're finished your queries, you can close the transaction by calling the commit() method.
$db->connection->commit();
If the previous queries fail, you can manually revert them by calling the rollBack() method. Before you do, you might want to check if there's an
existing transaction by using the inTransaction() method.
if ($db->connection->inTransaction()) {
$db->connection->rollBack();
}
This method can be useful before creating a new transaction since only one transaction can be active at a time.
Resources
DrawSQL
PHPiggy
Loading Files
In this lecture, we grabbed the contents of a file with the help of the file_get_contents() function. This function accepts a relative path to the
file. The file's contents are returned as a string.
$sqlFile = file_get_contents("./database.sql");
By adding these keywords, our database won't throw an error if the table already exists since duplicate tables are not allowed.
We installed a package called phpdotenv to help us with loading environment variables into our application. We used the following command to
install the package.
Resources
Environment files follow a key-value format. It's common to use uppercase letters for a variable name.
Next, we imported the Dotenv\Dotenv class. This class has a method called createImmutable to initialize the package. It accepts the path to the
file.
use Dotenv\Dotenv;
$dotenv = Dotenv::createImmutable(Paths::ROOT);
$dotenv->load();
In this example, we're passing in Paths::ROOT constant. Afterward, we call the load method to load the environment file.
The environment variables are stored in a superglobal variable by PHP called $_ENV . Here's an example of how we used these variables in our
database definition.
Resources
ENV Superglobal
composer.phar
/vendor/
.env
In addition, we created a .env.example file so that other developers can fill out the values for the environment variables when copying the
project.
$dependency = $factory($this);
Next, we used the container to help us grab the database so that we can pass it onto the UserService instance like so.
Resources
Built-in Functions
The fetchColumn() method automatically grabs the first item in the array.
Lastly, we used the results to check if there were any accounts found. If so, we threw the ValidationException with an error to describe the
email that was taken.
if ($emailCount > 0) {
throw new ValidationException(['email' => ['Email taken']]);
}
Understanding Hashing
This lecture does not have any notes. The content is meant to be consumed via video.
Resources
Hashing a Password
In this lecture, we hashed a password using the password_hash() function. This function has three arguments.
The second argument can be configured with a predefined PHP constant. You can refer to the documentation for a complete list of supported
algorithms. Bcrypt is the most popular algorithm. We can configure the bcrypt algorithm with the third argument. In this case, we're setting the
cost to 12 to allow the algorithm to use more resources.
Keep in mind, the bcrypt algorithm automatically salts our password and can generate a unique hash for even the same password.
Resources
pasword_hash() function
Resources
Login Template
We update the Database class to contain a method called find() , which calls the fetch() method on the connection.
In addition, we updated the PDO instance by passing in an array of configuration options to set the fetch mode to be an associative array.
After retrieving the record, we verified the passwords match using the password_verify() function. This function accepts the password in raw
text and the password in hashed form. We used the null-safe operator and typecasted the value into a string to reduce the likelihood of an error.
$passwordsMatch = password_verify(
$formData['password'],
$user['password'] ?? ''
);
Afterward, we performed a conditional statement to check if either variable was false, meaning the user provided invalid credentials.
if (!$user || !$passwordsMatch) {
throw new ValidationException([
'password' => 'Invalid email or password.'
]);
}
$_SESSION['user'] = $user['id'];
redirectTo("/");
session_set_cookie_params([
'secure' => $_ENV['APP_ENV'] === "production",
'httponly' => true,
'samesite' => 'lax'
]);
The session_set_cookie_params() function accepts an array of settings we'd like to configure. Here's what we updated.
secure - Checks if the user is on a secure connection. If they aren't, a cookie won't be sent.
httponly - Only allows a cookie to be used over HTTP. Disallows access to a cookie in JavaScript.
samesite - Determines where a cookie can be sent. If set to lax , allows for a cookie to be sent over an external link.
Resources
session_set_cookie_params() Function
Regenerating a Session ID
In this lecture, we regenerated the session ID after a user logs in. We shouldn't regenerate the ID on every request as the
session_regenerate_id() function is unreliable at times. It's recommended to update the ID when a user logs into an application and when they
log out. You can create a new ID like so:
session_regenerate_id();
Protecting Routes
In this lecture, we protected our routes from guests or authenticated users using middleware. In our middleware, we can check the
$_SESSION['user'] variable to verify the user is logged in. For example, let's say we wanted to check if the user is not logged in and redirect them
to the login page if they're not, we can use the following condition:
if (empty($_SESSION['user'])) {
redirectTo("/login");
}
We did something similar for routes that can only be accessed by guests called GuestOnlyMiddleware .
Resources
$this->routes[] = [
'path' => $path,
'method' => strtoupper($method),
'controller' => $controller,
'middlewares' => []
];
Next, we defined a method called addRouteMiddleware for adding new items to the middleware. Through this method, we're registering
middleware to the last route registered with our router by using the array_key_last() function. This function grabs the key from the last item in
the array. It accepts the array as an argument. With this information, we registered the middleware to the last item in the array.
$this->routes[$lastRouteKey]['middlewares'][] = $middleware;
}
Lastly, we merged the routes from the global middleware and route to a variable called $allMiddleware . We updated the loop to loop through this
array.
Middleware can be applied to a route by chaining the add() method and passing in the middleware to a route like so.
$app->get('/', [HomeController::class, 'home'])->add(AuthRequiredMiddleware::class);
unset($_SESSION['user']);
session_regenerate_id();
session_regenerate_id();
$_SESSION['user'] = $user['id'];
Understanding CSRF
This lecture does not have any notes. The content is meant to be consumed via video.
The token is stored as a session. We're also reusing the token so that we don't constantly create tokens on every request. Lastly, we injected the
token into our templates.
$this->view->addGlobal('csrfToken', $_SESSION['token']);
Resources
CSRF Middleware
Rendering Tokens
In this lecture, we created a partial called _csrf.twig . In this partial, we loaded the token as a hidden input.
Lastly, we loaded this partial into any template that has a form like so.
$requestMethod = strtoupper($_SERVER['REQUEST_METHOD']);
$validMethods = ['POST', 'PATCH', 'DELETE'];
if (!in_array($requestMethod, $validMethods)) {
$next();
return;
}
Next, we compared the token from the request with the token from the submission. If the tokens don't match, we have an invalid token. In that
case, the user is redirected to the homepage.
Lastly, we deleted the token since tokens should only be used once per form submission.
unset($_SESSION['token']);
Resources
One to many - A one-to-many relationship occurs when one record in table 1 is related to one or more records in table 2.
One-to-one - A one-to-one relationship in a database occurs when each row in table 1 has only one related row in table 2.
Many-to-many - A many-to-many relationship occurs when multiple records in one table are related to multiple records in another table.
Relationships are established with foreign keys. On a technical level, a foreign key is a constraint that links a column in one table to a column in a
different table and ensures that a value can be added if the same value already exists.
Next, we must provide the table that the current table should have a relationship with by adding the REFERENCES keyword. This keyword is
followed by the table name and the column name. Here's the entire query:
Resources
Validating Transactions
This lecture does not have any notes. The content is meant to be consumed via video.
if (empty($params[0])) {
throw new InvalidArgumentException('Maximum length not specified');
}
lengthMax:255
Resources
Validating Numbers
In this lecture, we defined a custom rule for validating if a value is a number. PHP has a function called is_numeric() that can check either
integers, floats, or strings to verify the value is a number. This function returns a boolean, which we can use to validate the value.
is_numeric($data[$field]);
Resources
Validating Dates
In this lecture, we created a custom rule for validating if a date is in a valid format. To perform this task, we used the date_parse_from_format()
function. This function grabs information about a date in any format. We can pass in the format and date as arguments.
If the function is not able to grab the date, it's because the date and value are not compatible. In that case, the return value will contain two keys
called error_count and warning_count with the number of errors with the value. We checked these keys to make sure they're set to 0 , which
means there aren't errors and the value is in the valid format.
Resources
Creating a Transaction
In this lecture, we inserted the transaction with the data from the form submission. Most of what we did is already familiar to us. The most
notable thing was the value of the date. By using the datetime data type, we must provide a date with the date and time. However, the form only
provides the date. We decided to add the time along with the date.
Resources
Datetime MariaDB
Retrieving Transactions
In this lecture, we retrieved a list of transactions uploaded by the user. Before doing so, we updated the Database class to define a method called
findAll() to return an array of results. Behind the scenes, this method calls the fetchAll() method from the PDOStatement class to grab all
results.
$this->statement->fetchAll();
Lastly, we must provide an alias/name for the new value by using the AS keyword followed by the name.
After querying the database, the new format will be accessible as formatted_date .
Resources
DATE_FORMAT Function
Query Parameters
In this lecture, we talked about query parameters. URLs can store data called query parameters. A query parameter can be applied to a URL by
adding a ? followed by a pair of key-values separated by the & character. Forms can add query parameters by setting the method attribute to
GET .
<form method="GET"></form>
In addition, each input must have a name attribute as it'll be used in the query parameter.
Lastly, we can use the $_GET superglobal variable to access a query parameter.
However, this performs an exact match. In some cases, you may want to perform a partial match. That can be performed with a LIKE condition.
You must add the % to either end of the string. If added to the end, the value will be compared to the beginning of the value in the column.
Whereas adding it to the beginning of the value will be compared to the end of the value in the column.
This solution isn't perfect. Typically, you would consider using a product such as Elastisearch to help you add advanced search features to your
application. This solution is meant to be a quick and simple way to add a search feature to a site. It may not always yield the most accurate
results.
Resources
Elastisearch
Filtering Transactions
In this lecture, we created a custom filter to apply our search using the LIKE condition. We created the following query.
In this example, we're adding an additional condition by comparing the description column against a value with the LIKE clause.
Limiting Results
In this lecture, we applied the LIMIT clause with Doctrine. First, we prepared variables for storing the length and offset. We're going to store the
current page in a query parameter called p .
$page = $_GET['p'] ?? 1;
$page = (int) $page;
$length = 3;
$offset = ($page - 1) * $length;
Next, we updated our query with the LIMIT and OFFSET keywords. The OFFSET keyword is an alternative solution to setting an offset. The
shorthand solution for the LIMIT keyword is optional. The main advantage of the OFFSET keyword is the extra clarity.
http_build_query([
'p' => $page - 1,
's' => $searchTerm
])
$transactionCount = $this->db->query(
"SELECT COUNT(*)
FROM transactions
WHERE user_id = :user_id
AND description LIKE :description",
$params
)->count();
Next, we returned the results along with the list of transactions in an array like so.
Lastly, we divided the total number of results by the length per page to know what the last page is. We surrounded this equation with the ceil()
function to round the result to an integer.
This is how we used the lastPage variable to render the link for the next page.
Next, we looped through the array using the array_map() function, where the callback function returns a query parameter with the page number
and search term.
We passed on this information to our template and rendered the links by looping through them with the foreach keyword like so.
<?php foreach ($pageLinks as $pageNum => $query) : ?>
<a href="/?<?php echo $query; ?>" class="<?php echo ($pageNum + 1) === $currentPage ? 'border-indigo-500 text-indigo-600' : 'bor
<?php echo $pageNum + 1; ?>
</a>
<?php endforeach; ?>
In this example, we're using the preg_replace() function to replace the placeholders with a regular expression. The regular expression grabs any
value inside the path segment.
$this->routes[] = [
'path' => $path,
'method' => strtoupper($method),
'controller' => $controller,
'middlewares' => [],
'regexPath' => $regexPath,
];
Resources
Regex101
Next, we removed the first item from the results since it's the entire path with the array_shift() function. This function accepts the array that
should have its first item removed.
array_shift($paramValues);
Afterward, we used a regular expression to extract the parameter names since the $paramValues variable only contains the values. We're trying
to connect the placeholder names with their respective values. We grabbed the names by using the preg_match_all() function. This function is
similar to the preg_match() function except it'll grab all results from a regular expression whereas the preg_match() function only grabs one
result.
Then, we combined the keys and values by using the array_combine() function. The first argument is the array of key names, and the second
argument is the values to assign to each key.
Since our date is stored with the datetime data type, we used the DATE_FORMAT function like so.
Resources
Updating a Transaction
This lecture does not have any notes. The content is meant to be consumed via video.
Afterward, we updated our App instance to register routes with the DELETE method.
return $this;
}
Lastly, we can override the method in our Router class by checking if the $_POST request contains the _method item. If so, we used this method
instead of the method sent with the browser.
Deleting a Transaction
This lecture does not have any notes. The content is meant to be consumed via video.
If you're using this attribute, the method attribute must be set to POST .
Next, you can view file data in PHP by using the superglobal $_FILES variable. This variable contains information on the files submitted with a
form, such as the file size and name.
dd($_FILES);
Resources
Next, we performed a conditional statement to check if the file was empty. We also checked if the file had any errors. PHP has a set of constants
for various errors. In this case, we're using the UPLOAD_ERR_OK constant to make sure there are no errors.
Resources
Upload Errors
Lastly, we compared the file size of the file by checking the $file['size'] variable. If the size exceeds the threshold, we threw an exception.
Resources
File Sizes
Validating Filenames
In this lecture, we validated the filename by using a regular expression. The filename can be grabbed with the $file['name'] variable.
$originalFilename = $file['name'];
Next, we used the preg_match() function to compare a string against a pattern. The regular expression we've written allows alphanumeric
characters along with . , _ , - , and characters.
if (!preg_match('/^[A-Za-z0-9._-\s]+$/', $originalFilename)) {
throw new ValidationException(['receipt' => ['Invalid filename']]);
}
$clientMimeType = $file['type'];
$allowedMimeTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($clientMimeType, $allowedMimeTypes)) {
throw new ValidationException(['receipt' => ['Invalid file type.']]);
}
Resources
Mime Types
Next, we generated a filename with the random_bytes() function, which accepts the size of the value. Next, converted the bytes into hex so that it
can be used as a filename. Lastly, we appended the extension.
if (!move_uploaded_file($file["tmp_name"], $uploadPath)) {
throw new ValidationException(['receipt' => ['Failed to upload file']]);
}
If the function returns false , this means the file could not be moved. In that case, we threw an exception to inform the user of the failure.
Resources
Receipt Entity
Displaying Receipts
This lecture does not have any notes. The content is meant to be consumed via video.
Downloading Files
In this lecture, we forced the user to download a receipt after visiting the route for downloading receipts. We configured the headers on the
response by calling the header() function. First, we set the Content-Disposition header to inline to allow the browser to display the file in the
browser. Alternatively, we can use attachment to completely force the user to download the file. Next, we set the filename property to configure
the name of the file.
Lastly, we attached the file to the body. Files can be read using the readfile() function. It accepts the path to the file.
readfile($filePath);
Deleting a Receipt
In this lecture, we deleted a file from our system. We can do so by using the unlink() function. This function accepts the path to the file to
delete. In this example, we passed the path to the receipt.
unlink($filePath);
Magic Numbers
In this lecture, we talked about magic numbers. They're numbers holding a special meaning where their intention is immediately clear. In these
cases, using constants to store magic numbers is beneficial. They provide the following benefits.
We created a constant for our storing a magic number for the HTTP status code to redirect users.
class Http
{
public const REDIRECT_STATUS_CODE = 302;
}
By doing so, we'll be able to use this constant in our redirectTo() function like so.
session_destroy();
In addition, we can expire a cookie using the setcookie() function. This function can be used for creating a cookie or modify an existing cookie.
In this case, we're using it to change the expiration date of the PHPSESSID cookie generated by PHP.
$params = session_get_cookie_params();
setcookie(
"PHPSESSID",
'',
time() - 3600,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
In this example, we're setting the PHPSESSID cookie to an empty value. Next, we're changing the expiration time by using the time() function to
grab the current time and subtracting from it to get a date earlier than the current time. This informs the browser the cookie expired a long time
ago.
Lastly, we're passing in the same values as the original cookie, which can be grabbed via the session_get_cookie_params() function.
Next, we defined a method responsible for dispatching the controller when a route couldn't be found.
$controllerInstance = $container ?
$container->resolve($class) :
new $class;
$action();
}
It's the same code as for rendering a route, but it doesn't have parameters, nor are we looping through the routes. We're just immediately using
the errorHandler property.
$app->setErrorHandler([ErrorController::class, 'notFound']);
Resources
Cloudways
HTAccess File
PHP Engineer