John's Technical Blog
Creating Deterministic UUIDs with UUIDv5 and X.500
2024-12-21
Introduction
When you need consistent, repeatable identifiers in your projects, UUIDv5 can be your go-to solution. Unlike UUIDv4, which is purely random, or other versions that are less random, but still unpredictable, UUIDv5 combines a namespace with a name (or set of attributes) to produce the same UUID for the same input pair regardless of the platform or programming language. This is highly useful when you have data that should map to a fixed identifier—like a country code or an organizational unit—where you always want to get back the exact same UUID for that data.
In the RFC 4122, Appendix C , there are four primary standard namespaces for UUIDv5:
- DNS (Domain Name System)
- URL (Uniform Resource Locator)
- OID (Object Identifier)
- X.500 (Directory Services)
In this post, we’ll:
- Introduce the four standard UUIDv5 namespaces: DNS, URL, OID, and X.500.
- Show simple examples using DNS, URL, and OID, including their full names and brief definitions.
- Dive deeper into the X.500 namespace.
- Discuss how to create UUIDv5-based database keys from existing uniqueness constraints or compound keys using the X.500 namespace.
We’ll use code samples from Symfony’s Symfony\Component\Uid\Uuid
component,
but UUIDv5 is available in several libraries across different platforms, which will produce the exact same deterministic UUIDs in all cases.
Quick Examples: DNS, URL, and OID Namespaces
Before diving into X.500, here are three simple examples demonstrating how to generate deterministic UUIDs using the DNS, URL, and OID namespaces.
1. DNS Example
- DNS stands for Domain Name System, a hierarchical naming system used to resolve domain names into IP addresses.
- A DNS name typically looks like
threeleaf.com
orsubdomain.example.org
.
use Symfony\Component\Uid\Uuid;
/**
* Generate a deterministic UUID using the DNS namespace.
*
* @param string $hostname The hostname to convert.
* @return string The deterministic UUID for the given hostname.
*/
function generateDnsUuid(string $hostname): string
{
/* Generate a deterministic UUIDv5: */
$dnsUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_DNS),
$hostname
);
return (string) $dnsUuid;
}
/* Example usage: */
$hostname = 'threeleaf.com';
echo generateDnsUuid($hostname);
2. URL Example
- URL stands for Uniform Resource Locator, a reference (an address) to a resource on the internet.
- A URL typically looks like
https://threeleaf.com/blog
.
/**
* Generate a deterministic UUID using the URL namespace.
*
* @param string $url The URL to convert.
* @return string The deterministic UUID for the given URL.
*/
function generateUrlUuid(string $url): string
{
/* Generate a deterministic UUIDv5: */
$urlUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_URL),
$url
);
return (string) $urlUuid;
}
/* Example usage: */
$url = 'https://threeleaf.com/blog';
echo generateUrlUuid($url);
3. OID Example
- OID stands for Object Identifier, a globally unique identifier used in various standards (e.g., SNMP, LDAP) to name an object or concept.
- An OID typically looks like
1.3.6.1.4.1...
, where each number identifies a node in a hierarchy.
/**
* Generate a deterministic UUID using the OID namespace.
*
* @param string $oid The OID string to convert.
* @return string The deterministic UUID for the given OID.
*/
function generateOidUuid(string $oid): string
{
/* Generate a deterministic UUIDv5: */
$oidUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_OID),
$oid
);
return (string) $oidUuid;
}
/* Example usage: */
$oid = '1.3.6.1.4.1';
echo generateOidUuid($oid);
Deep Dive: X.500 Namespaces and Distinguished Names
X.500 is a suite of standards for directory services. It defines a structure for Distinguished Names (DNs), which act like the “full path” for an entry in a directory (similar in concept to a file path in a filesystem). Each DN is a concatenation of attribute-value pairs that uniquely identify an entry. Common attribute keys include:
- CN (Common Name, Full Name)
- OU (Organizational Unit)
- O (Organization Name)
- L (Locality)
- ST (State or Province)
- C (ISO 3166 2-Letter Country Code)
- UID (User ID)
- SN (Surname, Last Name)
- GivenName (First Name)
- Mail (Email Address)
For a thorough list of attributes, check out:
Because these attributes are hierarchical and fairly stable
(e.g., CN=John A. Marsh
), the X.500 namespace is perfectly suited for generating
deterministic UUIDs when referencing directory-like entities.
Generating UUIDs with X.500
To generate a UUIDv5 based on a Distinguished Name, you can use the
NAMESPACE_X500
constant:
/**
* Generate a UUIDv5 based on a Distinguished Name (DN) using the X.500 namespace.
*
* @param string $distinguishedName The distinguished name to convert.
* @return string The deterministic UUID for the given distinguished name.
*/
function generateX500Uuid(string $distinguishedName): string
{
/* Generate a deterministic UUIDv5: */
$uuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$distinguishedName
);
return (string) $uuid;
}
/* Example usage: */
$distinguishedName = 'CN=John A. Marsh';
echo generateX500Uuid($distinguishedName);
$complexDistinguishedName = 'CN=Madison Stutts,OU=Engineering,O=Example Corp,C=US';
echo generateX500Uuid($complexDistinguishedName);
Important note about distinguished name volatility!
Depending on the specific use case, keep in mind that you may want to focus on attributes
that are stable and unique for your application. If you are independently calculating
deterministic UUIDs in several places over a long period of time, you will want to avoid
attributes that might change in that timespan. For example, an application-assigned user
ID or government-assigned ID might be considered permanent, whereas a user’s personal email
address or phone number might change.
Creating UUID Database Keys
In many applications, you already have unique identifiers—like an internal employee ID or a composite key based on a combination of columns. By converting these existing keys into UUIDv5 values (using NAMESPACE_X500 in this case), you ensure you still get deterministic UUIDs while leveraging your current uniqueness constraints.
1. Converting a Non-UUID Employee ID into a UUID
Suppose you have a table employee
with a unique, non-UUID primary key
employee_id
, such as 'EMP12345'
. Since
employeeNumber
is a standard X.500 attribute for an employee’s
unique ID, you could do:
/**
* Convert a non-UUID employee ID into a UUID.
*
* @param string $employeeId The employee ID to convert.
* @return string The deterministic UUID for the given employee ID.
*/
function generateEmployeeUuid(string $employeeId): string
{
/* Construct an X.500 "distinguished name" style string: */
$distinguishedName = 'employeeNumber=' . $employeeId;
/* Generate a deterministic UUIDv5: */
$employeeUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$distinguishedName
);
return (string) $employeeUuid;
}
/* Example usage: */
$employeeId = 'EMP12345';
echo generateEmployeeUuid($employeeId);
Here, every time you use "employeeNumber=EMP12345"
with
NAMESPACE_X500
, you’ll get the same UUID, which can be stored or used in
foreign keys.
2. Generating UUIDs from a Compound Key (Phone + Email)
Sometimes, uniqueness is enforced by a combination of columns—for example,
phone_number
and email
. Two relevant LDAP attributes here are
telephoneNumber
and mail
:
/**
* Generate a UUID from a compound key (phone + email).
*
* @param string $phoneNumber The customer phone number.
* @param string $email The customer email address.
* @return string The deterministic UUID for the given phone number and email.
*/
function generateCustomerUuid(string $phoneNumber, string $email): string
{
/* Create an X.500-style DN with multiple attributes: */
$compoundDn = 'telephoneNumber=' . $phoneNumber . ',mail=' . $email;
/* Generate the UUID using X.500 namespace: */
$userUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$compoundDn
);
return (string) $userUuid;
}
/* Example usage: */
$phoneNumber = '+1234567890';
$email = 'john.marsh@example.com';
echo generateCustomerUuid($phoneNumber, $email);
Conclusion
Deterministic UUIDs (UUIDv5) can simplify your data by ensuring the same input
always yields the same output. They’re particularly handy for X.500-style distinguished names,
where hierarchical attributes remain stable over time. By combining a robust namespace like
NAMESPACE_X500
with well-structured DNs, you’ll produce consistent identifiers
throughout your applications.
Whether you’re generating UUIDs for DNS, URL,
OID, or X.500, a Uuid
v5 function makes it
straightforward. Just remember to:
- Pick the right namespace (DNS, URL, OID, or X.500).
- Use suitable and stable attributes when generating UUIDs based on X.500 Distinguished Names.
- Leverage UUIDv5 to ensure deterministic results.
Finally, converting existing IDs or compound keys into UUIDv5 can unify how you manage references throughout your database and company. Once you adopt deterministic UUIDs, you’ll never have to worry about conflicting keys for the same underlying data.
Happy coding, and enjoy your deterministic UUIDs!
Appendix
Complete PHP Example (uuidv5-test.php)
require __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Uid\Uuid;
/**
* Generate a deterministic UUID using the DNS namespace.
*
* @param string $hostname The hostname to convert.
*
* @return string The deterministic UUID for the given hostname.
*/
function generateDnsUuid(string $hostname): string
{
/* Generate a deterministic UUIDv5: */
$dnsUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_DNS),
$hostname
);
return (string)$dnsUuid;
}
/**
* Generate a deterministic UUID using the URL namespace.
*
* @param string $url The URL to convert.
*
* @return string The deterministic UUID for the given URL.
*/
function generateUrlUuid(string $url): string
{
/* Generate a deterministic UUIDv5: */
$urlUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_URL),
$url
);
return (string)$urlUuid;
}
/**
* Generate a deterministic UUID using the OID namespace.
*
* @param string $oid The OID string to convert.
*
* @return string The deterministic UUID for the given OID.
*/
function generateOidUuid(string $oid): string
{
/* Generate a deterministic UUIDv5: */
$oidUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_OID),
$oid
);
return (string)$oidUuid;
}
/**
* Generate a UUIDv5 based on a Distinguished Name (DN) using the X.500 namespace.
*
* @param string $distinguishedName The distinguished name to convert.
*
* @return string The deterministic UUID for the given distinguished name.
*/
function generateX500Uuid(string $distinguishedName): string
{
/* Generate a deterministic UUIDv5: */
$uuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$distinguishedName
);
return (string)$uuid;
}
/**
* Convert a non-UUID employee ID into a UUID.
*
* @param string $employeeId The employee ID to convert.
*
* @return string The deterministic UUID for the given employee ID.
*/
function generateEmployeeUuid(string $employeeId): string
{
/* Construct an X.500 "distinguished name" style string: */
$distinguishedName = 'employeeNumber=' . $employeeId;
/* Generate a deterministic UUIDv5: */
$employeeUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$distinguishedName
);
return (string)$employeeUuid;
}
/**
* Generate a UUID from a compound key (phone + email).
*
* @param string $phoneNumber The customer phone number.
* @param string $email The customer email address.
*
* @return string The deterministic UUID for the given phone number and email.
*/
function generateCustomerUuid(string $phoneNumber, string $email): string
{
/* Create an X.500-style DN with multiple attributes: */
$compoundDn = 'telephoneNumber=' . $phoneNumber . ',mail=' . $email;
/* Generate the UUID using X.500 namespace: */
$userUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$compoundDn
);
return (string)$userUuid;
}
// Example usage section with formatted output:
echo 'Example Usage of UUID Generation Functions
';
echo "";
// DNS Example
$hostname = 'threeleaf.com';
$dnsResult = generateDnsUuid($hostname);
echo "";
echo "generateDnsUuid('$hostname')
";
echo "→ $dnsResult";
echo '';
// URL Example
$url = 'https://threeleaf.com/blog';
$urlResult = generateUrlUuid($url);
echo "";
echo "generateUrlUuid('$url')
";
echo "→ $urlResult";
echo '';
// OID Example
$oid = '1.3.6.1.4.1';
$oidResult = generateOidUuid($oid);
echo "";
echo "generateOidUuid('$oid')
";
echo "→ $oidResult";
echo '';
// X500 Simple Example
$distinguishedName = 'CN=John A. Marsh';
$x500Result = generateX500Uuid($distinguishedName);
echo "";
echo "generateX500Uuid('$distinguishedName')
";
echo "→ $x500Result";
echo '';
// X500 Complex Example
$complexDistinguishedName = 'CN=Madison Stutts,OU=Engineering,O=Example Corp,C=US';
$complexX500Result = generateX500Uuid($complexDistinguishedName);
echo "";
echo "generateX500Uuid('$complexDistinguishedName')
";
echo "→ $complexX500Result";
echo '';
// Employee Example
$employeeId = 'EMP12345';
$employeeResult = generateEmployeeUuid($employeeId);
echo "";
echo "generateEmployeeUuid('$employeeId')
";
echo "→ $employeeResult";
echo '';
// Customer Compound Key Example
$phoneNumber = '+1234567890';
$email = 'john.marsh@example.com';
$customerResult = generateCustomerUuid($phoneNumber, $email);
echo "";
echo "generateCustomerUuid('$phoneNumber', '$email')
";
echo "→ $customerResult";
echo '';
echo '';
Complete Python Example (uuidv5_test.py)
import uuid
from typing import Optional
def generate_dns_uuid(hostname: str) -> str:
"""Generate a deterministic UUID using the DNS namespace.
Args:
hostname: The hostname to convert.
Returns:
The deterministic UUID for the given hostname.
"""
return str(uuid.uuid5(uuid.NAMESPACE_DNS, hostname))
def generate_url_uuid(url: str) -> str:
"""Generate a deterministic UUID using the URL namespace.
Args:
url: The URL to convert.
Returns:
The deterministic UUID for the given URL.
"""
return str(uuid.uuid5(uuid.NAMESPACE_URL, url))
def generate_oid_uuid(oid: str) -> str:
"""Generate a deterministic UUID using the OID namespace.
Args:
oid: The OID string to convert.
Returns:
The deterministic UUID for the given OID.
"""
return str(uuid.uuid5(uuid.NAMESPACE_OID, oid))
def generate_x500_uuid(distinguished_name: str) -> str:
"""Generate a UUIDv5 based on a Distinguished Name (DN) using the X.500 namespace.
Args:
distinguished_name: The distinguished name to convert.
Returns:
The deterministic UUID for the given distinguished name.
"""
return str(uuid.uuid5(uuid.NAMESPACE_X500, distinguished_name))
def generate_employee_uuid(employee_id: str) -> str:
"""Convert a non-UUID employee ID into a UUID.
Args:
employee_id: The employee ID to convert.
Returns:
The deterministic UUID for the given employee ID.
"""
distinguished_name = f'employeeNumber={employee_id}'
return str(uuid.uuid5(uuid.NAMESPACE_X500, distinguished_name))
def generate_customer_uuid(phone_number: str, email: str) -> str:
"""Generate a UUID from a compound key (phone + email).
Args:
phone_number: The customer phone number.
email: The customer email address.
Returns:
The deterministic UUID for the given phone number and email.
"""
compound_dn = f'telephoneNumber={phone_number},mail={email}'
return str(uuid.uuid5(uuid.NAMESPACE_X500, compound_dn))
def print_example_header():
print("Example Usage of UUID Generation Functions
")
print("")
def print_example_result(function_name: str, args: str, result: str):
print("")
print(f"{function_name}({args})
")
print(f"→ {result}")
print("")
def main():
print_example_header()
# DNS Example
hostname = 'threeleaf.com'
dns_result = generate_dns_uuid(hostname)
print_example_result('generate_dns_uuid', f"'{hostname}'", dns_result)
# URL Example
url = 'https://threeleaf.com/blog'
url_result = generate_url_uuid(url)
print_example_result('generate_url_uuid', f"'{url}'", url_result)
# OID Example
oid = '1.3.6.1.4.1'
oid_result = generate_oid_uuid(oid)
print_example_result('generate_oid_uuid', f"'{oid}'", oid_result)
# X500 Simple Example
distinguished_name = 'CN=John A. Marsh'
x500_result = generate_x500_uuid(distinguished_name)
print_example_result('generate_x500_uuid', f"'{distinguished_name}'", x500_result)
# X500 Complex Example
complex_dn = 'CN=Madison Stutts,OU=Engineering,O=Example Corp,C=US'
complex_result = generate_x500_uuid(complex_dn)
print_example_result('generate_x500_uuid', f"'{complex_dn}'", complex_result)
# Employee Example
employee_id = 'EMP12345'
employee_result = generate_employee_uuid(employee_id)
print_example_result('generate_employee_uuid', f"'{employee_id}'", employee_result)
# Customer Compound Key Example
phone_number = '+1234567890'
email = 'john.marsh@example.com'
customer_result = generate_customer_uuid(phone_number, email)
print_example_result('generate_customer_uuid',
f"'{phone_number}', '{email}'",
customer_result)
print("")
if __name__ == "__main__":
main()
Output (matches between PHP runnin on Linux and Python running on Macbook)
Example Usage of UUID Generation Functions
→ d4a08aa5-9661-57ab-bf61-8f28be9b1f00
→ b0277088-caf5-5a15-aa5d-5115112640c3
→ 106dd502-8b3e-50db-80ed-1134f5c18eae
→ a953bc33-f538-5bb3-baa3-aaf081b5df93
→ b837ef27-6f4f-5a48-9419-badb502fb581
→ 7aed46df-9f8e-5252-b340-34a0f15d33dd
→ db23fe5d-ec32-5522-b563-f10847432d04
Listen to the podcast generated by NotebookLM
Creating Deterministic UUIDs with UUIDv5 and X.500
2024-12-21
Introduction
When you need consistent, repeatable identifiers in your projects, UUIDv5 can be your go-to solution. Unlike UUIDv4, which is purely random, or other versions that are less random, but still unpredictable, UUIDv5 combines a namespace with a name (or set of attributes) to produce the same UUID for the same input pair regardless of the platform or programming language. This is highly useful when you have data that should map to a fixed identifier—like a country code or an organizational unit—where you always want to get back the exact same UUID for that data.
In the RFC 4122, Appendix C , there are four primary standard namespaces for UUIDv5:
- DNS (Domain Name System)
- URL (Uniform Resource Locator)
- OID (Object Identifier)
- X.500 (Directory Services)
In this post, we’ll:
- Introduce the four standard UUIDv5 namespaces: DNS, URL, OID, and X.500.
- Show simple examples using DNS, URL, and OID, including their full names and brief definitions.
- Dive deeper into the X.500 namespace.
- Discuss how to create UUIDv5-based database keys from existing uniqueness constraints or compound keys using the X.500 namespace.
We’ll use code samples from Symfony’s Symfony\Component\Uid\Uuid
component,
but UUIDv5 is available in several libraries across different platforms, which will produce the exact same deterministic UUIDs in all cases.
Quick Examples: DNS, URL, and OID Namespaces
Before diving into X.500, here are three simple examples demonstrating how to generate deterministic UUIDs using the DNS, URL, and OID namespaces.
1. DNS Example
- DNS stands for Domain Name System, a hierarchical naming system used to resolve domain names into IP addresses.
- A DNS name typically looks like
threeleaf.com
orsubdomain.example.org
.
use Symfony\Component\Uid\Uuid;
/**
* Generate a deterministic UUID using the DNS namespace.
*
* @param string $hostname The hostname to convert.
* @return string The deterministic UUID for the given hostname.
*/
function generateDnsUuid(string $hostname): string
{
/* Generate a deterministic UUIDv5: */
$dnsUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_DNS),
$hostname
);
return (string) $dnsUuid;
}
/* Example usage: */
$hostname = 'threeleaf.com';
echo generateDnsUuid($hostname);
2. URL Example
- URL stands for Uniform Resource Locator, a reference (an address) to a resource on the internet.
- A URL typically looks like
https://threeleaf.com/blog
.
/**
* Generate a deterministic UUID using the URL namespace.
*
* @param string $url The URL to convert.
* @return string The deterministic UUID for the given URL.
*/
function generateUrlUuid(string $url): string
{
/* Generate a deterministic UUIDv5: */
$urlUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_URL),
$url
);
return (string) $urlUuid;
}
/* Example usage: */
$url = 'https://threeleaf.com/blog';
echo generateUrlUuid($url);
3. OID Example
- OID stands for Object Identifier, a globally unique identifier used in various standards (e.g., SNMP, LDAP) to name an object or concept.
- An OID typically looks like
1.3.6.1.4.1...
, where each number identifies a node in a hierarchy.
/**
* Generate a deterministic UUID using the OID namespace.
*
* @param string $oid The OID string to convert.
* @return string The deterministic UUID for the given OID.
*/
function generateOidUuid(string $oid): string
{
/* Generate a deterministic UUIDv5: */
$oidUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_OID),
$oid
);
return (string) $oidUuid;
}
/* Example usage: */
$oid = '1.3.6.1.4.1';
echo generateOidUuid($oid);
Deep Dive: X.500 Namespaces and Distinguished Names
X.500 is a suite of standards for directory services. It defines a structure for Distinguished Names (DNs), which act like the “full path” for an entry in a directory (similar in concept to a file path in a filesystem). Each DN is a concatenation of attribute-value pairs that uniquely identify an entry. Common attribute keys include:
- CN (Common Name, Full Name)
- OU (Organizational Unit)
- O (Organization Name)
- L (Locality)
- ST (State or Province)
- C (ISO 3166 2-Letter Country Code)
- UID (User ID)
- SN (Surname, Last Name)
- GivenName (First Name)
- Mail (Email Address)
For a thorough list of attributes, check out:
Because these attributes are hierarchical and fairly stable
(e.g., CN=John A. Marsh
), the X.500 namespace is perfectly suited for generating
deterministic UUIDs when referencing directory-like entities.
Generating UUIDs with X.500
To generate a UUIDv5 based on a Distinguished Name, you can use the
NAMESPACE_X500
constant:
/**
* Generate a UUIDv5 based on a Distinguished Name (DN) using the X.500 namespace.
*
* @param string $distinguishedName The distinguished name to convert.
* @return string The deterministic UUID for the given distinguished name.
*/
function generateX500Uuid(string $distinguishedName): string
{
/* Generate a deterministic UUIDv5: */
$uuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$distinguishedName
);
return (string) $uuid;
}
/* Example usage: */
$distinguishedName = 'CN=John A. Marsh';
echo generateX500Uuid($distinguishedName);
$complexDistinguishedName = 'CN=Madison Stutts,OU=Engineering,O=Example Corp,C=US';
echo generateX500Uuid($complexDistinguishedName);
Important note about distinguished name volatility!
Depending on the specific use case, keep in mind that you may want to focus on attributes
that are stable and unique for your application. If you are independently calculating
deterministic UUIDs in several places over a long period of time, you will want to avoid
attributes that might change in that timespan. For example, an application-assigned user
ID or government-assigned ID might be considered permanent, whereas a user’s personal email
address or phone number might change.
Creating UUID Database Keys
In many applications, you already have unique identifiers—like an internal employee ID or a composite key based on a combination of columns. By converting these existing keys into UUIDv5 values (using NAMESPACE_X500 in this case), you ensure you still get deterministic UUIDs while leveraging your current uniqueness constraints.
1. Converting a Non-UUID Employee ID into a UUID
Suppose you have a table employee
with a unique, non-UUID primary key
employee_id
, such as 'EMP12345'
. Since
employeeNumber
is a standard X.500 attribute for an employee’s
unique ID, you could do:
/**
* Convert a non-UUID employee ID into a UUID.
*
* @param string $employeeId The employee ID to convert.
* @return string The deterministic UUID for the given employee ID.
*/
function generateEmployeeUuid(string $employeeId): string
{
/* Construct an X.500 "distinguished name" style string: */
$distinguishedName = 'employeeNumber=' . $employeeId;
/* Generate a deterministic UUIDv5: */
$employeeUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$distinguishedName
);
return (string) $employeeUuid;
}
/* Example usage: */
$employeeId = 'EMP12345';
echo generateEmployeeUuid($employeeId);
Here, every time you use "employeeNumber=EMP12345"
with
NAMESPACE_X500
, you’ll get the same UUID, which can be stored or used in
foreign keys.
2. Generating UUIDs from a Compound Key (Phone + Email)
Sometimes, uniqueness is enforced by a combination of columns—for example,
phone_number
and email
. Two relevant LDAP attributes here are
telephoneNumber
and mail
:
/**
* Generate a UUID from a compound key (phone + email).
*
* @param string $phoneNumber The customer phone number.
* @param string $email The customer email address.
* @return string The deterministic UUID for the given phone number and email.
*/
function generateCustomerUuid(string $phoneNumber, string $email): string
{
/* Create an X.500-style DN with multiple attributes: */
$compoundDn = 'telephoneNumber=' . $phoneNumber . ',mail=' . $email;
/* Generate the UUID using X.500 namespace: */
$userUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$compoundDn
);
return (string) $userUuid;
}
/* Example usage: */
$phoneNumber = '+1234567890';
$email = 'john.marsh@example.com';
echo generateCustomerUuid($phoneNumber, $email);
Conclusion
Deterministic UUIDs (UUIDv5) can simplify your data by ensuring the same input
always yields the same output. They’re particularly handy for X.500-style distinguished names,
where hierarchical attributes remain stable over time. By combining a robust namespace like
NAMESPACE_X500
with well-structured DNs, you’ll produce consistent identifiers
throughout your applications.
Whether you’re generating UUIDs for DNS, URL,
OID, or X.500, a Uuid
v5 function makes it
straightforward. Just remember to:
- Pick the right namespace (DNS, URL, OID, or X.500).
- Use suitable and stable attributes when generating UUIDs based on X.500 Distinguished Names.
- Leverage UUIDv5 to ensure deterministic results.
Finally, converting existing IDs or compound keys into UUIDv5 can unify how you manage references throughout your database and company. Once you adopt deterministic UUIDs, you’ll never have to worry about conflicting keys for the same underlying data.
Happy coding, and enjoy your deterministic UUIDs!
Appendix
Complete PHP Example (uuidv5-test.php)
require __DIR__ . '/../vendor/autoload.php';
use Symfony\Component\Uid\Uuid;
/**
* Generate a deterministic UUID using the DNS namespace.
*
* @param string $hostname The hostname to convert.
*
* @return string The deterministic UUID for the given hostname.
*/
function generateDnsUuid(string $hostname): string
{
/* Generate a deterministic UUIDv5: */
$dnsUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_DNS),
$hostname
);
return (string)$dnsUuid;
}
/**
* Generate a deterministic UUID using the URL namespace.
*
* @param string $url The URL to convert.
*
* @return string The deterministic UUID for the given URL.
*/
function generateUrlUuid(string $url): string
{
/* Generate a deterministic UUIDv5: */
$urlUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_URL),
$url
);
return (string)$urlUuid;
}
/**
* Generate a deterministic UUID using the OID namespace.
*
* @param string $oid The OID string to convert.
*
* @return string The deterministic UUID for the given OID.
*/
function generateOidUuid(string $oid): string
{
/* Generate a deterministic UUIDv5: */
$oidUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_OID),
$oid
);
return (string)$oidUuid;
}
/**
* Generate a UUIDv5 based on a Distinguished Name (DN) using the X.500 namespace.
*
* @param string $distinguishedName The distinguished name to convert.
*
* @return string The deterministic UUID for the given distinguished name.
*/
function generateX500Uuid(string $distinguishedName): string
{
/* Generate a deterministic UUIDv5: */
$uuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$distinguishedName
);
return (string)$uuid;
}
/**
* Convert a non-UUID employee ID into a UUID.
*
* @param string $employeeId The employee ID to convert.
*
* @return string The deterministic UUID for the given employee ID.
*/
function generateEmployeeUuid(string $employeeId): string
{
/* Construct an X.500 "distinguished name" style string: */
$distinguishedName = 'employeeNumber=' . $employeeId;
/* Generate a deterministic UUIDv5: */
$employeeUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$distinguishedName
);
return (string)$employeeUuid;
}
/**
* Generate a UUID from a compound key (phone + email).
*
* @param string $phoneNumber The customer phone number.
* @param string $email The customer email address.
*
* @return string The deterministic UUID for the given phone number and email.
*/
function generateCustomerUuid(string $phoneNumber, string $email): string
{
/* Create an X.500-style DN with multiple attributes: */
$compoundDn = 'telephoneNumber=' . $phoneNumber . ',mail=' . $email;
/* Generate the UUID using X.500 namespace: */
$userUuid = Uuid::v5(
Uuid::fromString(Uuid::NAMESPACE_X500),
$compoundDn
);
return (string)$userUuid;
}
// Example usage section with formatted output:
echo 'Example Usage of UUID Generation Functions
';
echo "";
// DNS Example
$hostname = 'threeleaf.com';
$dnsResult = generateDnsUuid($hostname);
echo "";
echo "generateDnsUuid('$hostname')
";
echo "→ $dnsResult";
echo '';
// URL Example
$url = 'https://threeleaf.com/blog';
$urlResult = generateUrlUuid($url);
echo "";
echo "generateUrlUuid('$url')
";
echo "→ $urlResult";
echo '';
// OID Example
$oid = '1.3.6.1.4.1';
$oidResult = generateOidUuid($oid);
echo "";
echo "generateOidUuid('$oid')
";
echo "→ $oidResult";
echo '';
// X500 Simple Example
$distinguishedName = 'CN=John A. Marsh';
$x500Result = generateX500Uuid($distinguishedName);
echo "";
echo "generateX500Uuid('$distinguishedName')
";
echo "→ $x500Result";
echo '';
// X500 Complex Example
$complexDistinguishedName = 'CN=Madison Stutts,OU=Engineering,O=Example Corp,C=US';
$complexX500Result = generateX500Uuid($complexDistinguishedName);
echo "";
echo "generateX500Uuid('$complexDistinguishedName')
";
echo "→ $complexX500Result";
echo '';
// Employee Example
$employeeId = 'EMP12345';
$employeeResult = generateEmployeeUuid($employeeId);
echo "";
echo "generateEmployeeUuid('$employeeId')
";
echo "→ $employeeResult";
echo '';
// Customer Compound Key Example
$phoneNumber = '+1234567890';
$email = 'john.marsh@example.com';
$customerResult = generateCustomerUuid($phoneNumber, $email);
echo "";
echo "generateCustomerUuid('$phoneNumber', '$email')
";
echo "→ $customerResult";
echo '';
echo '';
Complete Python Example (uuidv5_test.py)
import uuid
from typing import Optional
def generate_dns_uuid(hostname: str) -> str:
"""Generate a deterministic UUID using the DNS namespace.
Args:
hostname: The hostname to convert.
Returns:
The deterministic UUID for the given hostname.
"""
return str(uuid.uuid5(uuid.NAMESPACE_DNS, hostname))
def generate_url_uuid(url: str) -> str:
"""Generate a deterministic UUID using the URL namespace.
Args:
url: The URL to convert.
Returns:
The deterministic UUID for the given URL.
"""
return str(uuid.uuid5(uuid.NAMESPACE_URL, url))
def generate_oid_uuid(oid: str) -> str:
"""Generate a deterministic UUID using the OID namespace.
Args:
oid: The OID string to convert.
Returns:
The deterministic UUID for the given OID.
"""
return str(uuid.uuid5(uuid.NAMESPACE_OID, oid))
def generate_x500_uuid(distinguished_name: str) -> str:
"""Generate a UUIDv5 based on a Distinguished Name (DN) using the X.500 namespace.
Args:
distinguished_name: The distinguished name to convert.
Returns:
The deterministic UUID for the given distinguished name.
"""
return str(uuid.uuid5(uuid.NAMESPACE_X500, distinguished_name))
def generate_employee_uuid(employee_id: str) -> str:
"""Convert a non-UUID employee ID into a UUID.
Args:
employee_id: The employee ID to convert.
Returns:
The deterministic UUID for the given employee ID.
"""
distinguished_name = f'employeeNumber={employee_id}'
return str(uuid.uuid5(uuid.NAMESPACE_X500, distinguished_name))
def generate_customer_uuid(phone_number: str, email: str) -> str:
"""Generate a UUID from a compound key (phone + email).
Args:
phone_number: The customer phone number.
email: The customer email address.
Returns:
The deterministic UUID for the given phone number and email.
"""
compound_dn = f'telephoneNumber={phone_number},mail={email}'
return str(uuid.uuid5(uuid.NAMESPACE_X500, compound_dn))
def print_example_header():
print("Example Usage of UUID Generation Functions
")
print("")
def print_example_result(function_name: str, args: str, result: str):
print("")
print(f"{function_name}({args})
")
print(f"→ {result}")
print("")
def main():
print_example_header()
# DNS Example
hostname = 'threeleaf.com'
dns_result = generate_dns_uuid(hostname)
print_example_result('generate_dns_uuid', f"'{hostname}'", dns_result)
# URL Example
url = 'https://threeleaf.com/blog'
url_result = generate_url_uuid(url)
print_example_result('generate_url_uuid', f"'{url}'", url_result)
# OID Example
oid = '1.3.6.1.4.1'
oid_result = generate_oid_uuid(oid)
print_example_result('generate_oid_uuid', f"'{oid}'", oid_result)
# X500 Simple Example
distinguished_name = 'CN=John A. Marsh'
x500_result = generate_x500_uuid(distinguished_name)
print_example_result('generate_x500_uuid', f"'{distinguished_name}'", x500_result)
# X500 Complex Example
complex_dn = 'CN=Madison Stutts,OU=Engineering,O=Example Corp,C=US'
complex_result = generate_x500_uuid(complex_dn)
print_example_result('generate_x500_uuid', f"'{complex_dn}'", complex_result)
# Employee Example
employee_id = 'EMP12345'
employee_result = generate_employee_uuid(employee_id)
print_example_result('generate_employee_uuid', f"'{employee_id}'", employee_result)
# Customer Compound Key Example
phone_number = '+1234567890'
email = 'john.marsh@example.com'
customer_result = generate_customer_uuid(phone_number, email)
print_example_result('generate_customer_uuid',
f"'{phone_number}', '{email}'",
customer_result)
print("")
if __name__ == "__main__":
main()
Output (matches between PHP runnin on Linux and Python running on Macbook)
Example Usage of UUID Generation Functions
→ d4a08aa5-9661-57ab-bf61-8f28be9b1f00
→ b0277088-caf5-5a15-aa5d-5115112640c3
→ 106dd502-8b3e-50db-80ed-1134f5c18eae
→ a953bc33-f538-5bb3-baa3-aaf081b5df93
→ b837ef27-6f4f-5a48-9419-badb502fb581
→ 7aed46df-9f8e-5252-b340-34a0f15d33dd
→ db23fe5d-ec32-5522-b563-f10847432d04
Listen to the podcast generated by NotebookLM
Understanding SAML: A Technical Guide for Developers
2024-11-22
Introduction
Security Assertion Markup Language (SAML) is a standard for exchanging authentication and authorization data between parties. It enables Single Sign-On (SSO), simplifying access to multiple services with a single set of credentials. For developers new to SAML, it can seem complex, but understanding its components and workflows can demystify the process and empower you to implement secure authentication systems.
This post will provide an overview of SAML, followed by a technical deep dive into its key components and how they interact.
What is SAML?
SAML is an XML-based framework for exchanging user authentication and authorization data between two main entities:
• Service Provider (SP): The application or service a user wants to access.
• Identity Provider (IdP): The entity responsible for authenticating the user and providing identity information to the SP.
The goal of SAML is to allow the SP to rely on the IdP to authenticate users, removing the need for the SP to handle authentication directly.
Key Concepts in SAML
1. SingleSignOnService (SSO)
The SingleSignOnService is a core feature of SAML. It allows users to authenticate once with the IdP and gain access to multiple SPs without re-entering credentials. This is achieved using SAML assertions, which are secure XML documents containing authentication and authorization data.
Workflow:
1. A user attempts to access a protected resource at the SP.
2. The SP generates a SAML Authentication Request and redirects the user to the IdP.
3. The IdP authenticates the user (e.g., via username/password or multi-factor authentication).
4. The IdP generates a SAML Assertion, signs it, and redirects the user back to the SP.
5. The SP validates the assertion and grants the user access.
2. SingleLogoutService (SLO)
The SingleLogoutService ensures that when a user logs out from one SP, they are also logged out from all other connected SPs and the IdP.
Workflow:
1. A user initiates logout at an SP.
2. The SP sends a SAML Logout Request to the IdP.
3. The IdP propagates the logout to other SPs where the user has active sessions.
4. The IdP sends a SAML Logout Response to the initiating SP to confirm the logout.
SLO is critical for maintaining session consistency and preventing orphaned sessions across services.
3. Service Provider (SP)
The SP is the consumer of SAML assertions. It relies on the IdP to authenticate users. SPs trust the IdP’s assertions because they are digitally signed, ensuring integrity and authenticity.
Key Responsibilities:
• Generate SAML Authentication Requests.
• Validate SAML Assertions received from the IdP.
• Enforce authorization rules based on attributes in the assertion.
4. Identity Provider (IdP)
The IdP is the authority that authenticates users and issues SAML assertions. It is the cornerstone of trust in a SAML setup.
Key Responsibilities:
• Authenticate users securely.
• Provide identity information and attributes to SPs.
• Manage the trust relationship with SPs, usually established via certificates.
5. SAML Federation
SAML federation refers to the establishment of trust relationships between multiple SPs and IdPs. This allows seamless interoperability in environments where multiple organizations or services share a common user base.
Federation typically involves:
• Exchanging metadata files, which include details about endpoints, certificates, and supported bindings.
• Agreeing on shared identifiers and attributes (e.g., email, user ID).
• Setting up signing and encryption certificates for secure communication.
How Authentication Data is Transferred in SAML
The SAML protocol involves several key steps:
1. Authentication Request (AuthnRequest):
• The SP sends an AuthnRequest to the IdP’s SingleSignOnService endpoint.
• This request includes metadata like the SP’s unique identifier, requested bindings, and a unique ID.
2. User Authentication:
• The IdP authenticates the user via its chosen method (e.g., credentials, biometrics).
• Upon success, the IdP generates a SAML assertion.
3. SAML Assertion:
• The assertion contains:
• Subject: The authenticated user’s identifier (e.g., email or username).
• Conditions: Validity period and audience restrictions.
• Attributes: Optional user attributes (e.g., roles, groups).
• Authentication Statement: Details about the authentication event.
• The assertion is signed using the IdP’s private key for integrity and authenticity.
4. Response:
• The IdP sends the SAML assertion in a SAML Response back to the SP, often via the user’s browser.
• The SP validates the response using the IdP’s public key.
5. Access Grant:
• Upon successful validation, the SP grants the user access to the requested resource.
SAML Bindings: Transporting Data
SAML defines several “bindings” for transporting messages between SPs and IdPs:
• HTTP Redirect Binding: Typically used for AuthnRequests, where the request is encoded in a URL.
• HTTP POST Binding: Used for SAML Responses, where the assertion is embedded in an HTML form.
• SOAP Binding: Often used for back-channel communications like SingleLogoutService.
Each binding ensures secure and reliable transmission of data.
Security Considerations
• Signing and Encryption: All assertions and responses should be signed, and sensitive data can be encrypted to prevent tampering and eavesdropping.
• Replay Attacks: Use unique IDs and timestamps in SAML messages to prevent reuse.
• Certificate Management: Regularly update and rotate signing and encryption certificates to maintain trust.
Practical Implementation Tips
1. Leverage Existing Libraries: Use established SAML libraries (e.g., simplesamlphp, OneLogin for Python, Sustainsys.Saml2 for .NET) to handle complex tasks like request generation and assertion validation.
2. Understand Metadata: Exchange and validate metadata files to establish trust between SPs and IdPs.
3. Test Extensively: Use tools like SAML-tracer or browser developer tools to debug SAML message flows.
4. Monitor Logs: Both SPs and IdPs should log SAML events for troubleshooting and auditing.
Conclusion
SAML is a robust framework for implementing SSO and federated identity management. By understanding its components—SP, IdP, assertions, and services like SSO and SLO—you can build secure and user-friendly authentication systems. While the initial learning curve may be steep, the benefits of implementing SAML make it worth the effort.
If you’re ready to get started, dive into the configuration and setup of your IdP and SP, and let SAML simplify authentication for your users.
Understanding SAML: A Technical Guide for Developers
2024-11-22
Introduction
Security Assertion Markup Language (SAML) is a standard for exchanging authentication and authorization data between parties. It enables Single Sign-On (SSO), simplifying access to multiple services with a single set of credentials. For developers new to SAML, it can seem complex, but understanding its components and workflows can demystify the process and empower you to implement secure authentication systems.
This post will provide an overview of SAML, followed by a technical deep dive into its key components and how they interact.
What is SAML?
SAML is an XML-based framework for exchanging user authentication and authorization data between two main entities:
• Service Provider (SP): The application or service a user wants to access.
• Identity Provider (IdP): The entity responsible for authenticating the user and providing identity information to the SP.
The goal of SAML is to allow the SP to rely on the IdP to authenticate users, removing the need for the SP to handle authentication directly.
Key Concepts in SAML
1. SingleSignOnService (SSO)
The SingleSignOnService is a core feature of SAML. It allows users to authenticate once with the IdP and gain access to multiple SPs without re-entering credentials. This is achieved using SAML assertions, which are secure XML documents containing authentication and authorization data.
Workflow:
1. A user attempts to access a protected resource at the SP.
2. The SP generates a SAML Authentication Request and redirects the user to the IdP.
3. The IdP authenticates the user (e.g., via username/password or multi-factor authentication).
4. The IdP generates a SAML Assertion, signs it, and redirects the user back to the SP.
5. The SP validates the assertion and grants the user access.
2. SingleLogoutService (SLO)
The SingleLogoutService ensures that when a user logs out from one SP, they are also logged out from all other connected SPs and the IdP.
Workflow:
1. A user initiates logout at an SP.
2. The SP sends a SAML Logout Request to the IdP.
3. The IdP propagates the logout to other SPs where the user has active sessions.
4. The IdP sends a SAML Logout Response to the initiating SP to confirm the logout.
SLO is critical for maintaining session consistency and preventing orphaned sessions across services.
3. Service Provider (SP)
The SP is the consumer of SAML assertions. It relies on the IdP to authenticate users. SPs trust the IdP’s assertions because they are digitally signed, ensuring integrity and authenticity.
Key Responsibilities:
• Generate SAML Authentication Requests.
• Validate SAML Assertions received from the IdP.
• Enforce authorization rules based on attributes in the assertion.
4. Identity Provider (IdP)
The IdP is the authority that authenticates users and issues SAML assertions. It is the cornerstone of trust in a SAML setup.
Key Responsibilities:
• Authenticate users securely.
• Provide identity information and attributes to SPs.
• Manage the trust relationship with SPs, usually established via certificates.
5. SAML Federation
SAML federation refers to the establishment of trust relationships between multiple SPs and IdPs. This allows seamless interoperability in environments where multiple organizations or services share a common user base.
Federation typically involves:
• Exchanging metadata files, which include details about endpoints, certificates, and supported bindings.
• Agreeing on shared identifiers and attributes (e.g., email, user ID).
• Setting up signing and encryption certificates for secure communication.
How Authentication Data is Transferred in SAML
The SAML protocol involves several key steps:
1. Authentication Request (AuthnRequest):
• The SP sends an AuthnRequest to the IdP’s SingleSignOnService endpoint.
• This request includes metadata like the SP’s unique identifier, requested bindings, and a unique ID.
2. User Authentication:
• The IdP authenticates the user via its chosen method (e.g., credentials, biometrics).
• Upon success, the IdP generates a SAML assertion.
3. SAML Assertion:
• The assertion contains:
• Subject: The authenticated user’s identifier (e.g., email or username).
• Conditions: Validity period and audience restrictions.
• Attributes: Optional user attributes (e.g., roles, groups).
• Authentication Statement: Details about the authentication event.
• The assertion is signed using the IdP’s private key for integrity and authenticity.
4. Response:
• The IdP sends the SAML assertion in a SAML Response back to the SP, often via the user’s browser.
• The SP validates the response using the IdP’s public key.
5. Access Grant:
• Upon successful validation, the SP grants the user access to the requested resource.
SAML Bindings: Transporting Data
SAML defines several “bindings” for transporting messages between SPs and IdPs:
• HTTP Redirect Binding: Typically used for AuthnRequests, where the request is encoded in a URL.
• HTTP POST Binding: Used for SAML Responses, where the assertion is embedded in an HTML form.
• SOAP Binding: Often used for back-channel communications like SingleLogoutService.
Each binding ensures secure and reliable transmission of data.
Security Considerations
• Signing and Encryption: All assertions and responses should be signed, and sensitive data can be encrypted to prevent tampering and eavesdropping.
• Replay Attacks: Use unique IDs and timestamps in SAML messages to prevent reuse.
• Certificate Management: Regularly update and rotate signing and encryption certificates to maintain trust.
Practical Implementation Tips
1. Leverage Existing Libraries: Use established SAML libraries (e.g., simplesamlphp, OneLogin for Python, Sustainsys.Saml2 for .NET) to handle complex tasks like request generation and assertion validation.
2. Understand Metadata: Exchange and validate metadata files to establish trust between SPs and IdPs.
3. Test Extensively: Use tools like SAML-tracer or browser developer tools to debug SAML message flows.
4. Monitor Logs: Both SPs and IdPs should log SAML events for troubleshooting and auditing.
Conclusion
SAML is a robust framework for implementing SSO and federated identity management. By understanding its components—SP, IdP, assertions, and services like SSO and SLO—you can build secure and user-friendly authentication systems. While the initial learning curve may be steep, the benefits of implementing SAML make it worth the effort.
If you’re ready to get started, dive into the configuration and setup of your IdP and SP, and let SAML simplify authentication for your users.
Creating Custom Zip Files with Git
2024-11-15
Managing project distributions often requires creating archives that exclude certain files or directories. Git offers a streamlined approach to this through the .gitattributes
file, enabling precise control over the
contents of your archives.
Why Use Git for Custom Archives?
Utilizing Git's archiving capabilities provides several advantages:
- Consistency: Ensures that archives are generated from a specific commit, maintaining version integrity.
- Automation: Facilitates the creation of scripts for automated deployment processes.
- Customization: Allows exclusion of unnecessary files, resulting in cleaner and more efficient distributions.
Understanding the .gitattributes
Configuration
The .gitattributes
file defines how Git handles various files within your repository. Below is an example configuration:
Let's break down this configuration:
- General Settings:
* text=auto eol=lf
: Normalizes line endings to LF, ensuring consistency across different operating systems.*.php diff=php
: Specifies that PHP files should use PHP-specific diff settings.
- Export Ignoring: The
export-ignore
attribute tells Git to exclude specified files or directories from the archive:.editorconfig
,.gitattributes
,.gitignore
: Configuration files not needed in the distribution./.git
,/.idea
: Version control and IDE-specific directories./tests
,/util
,/vendor
: Directories containing tests, utilities, and dependencies that may not be necessary for the end-user.phpunit.xml
: Configuration file for PHPUnit, typically not required in the final product.
Creating the Archive
With the .gitattributes
file configured, you can create a zip archive using the following command:
git archive --worktree-attributes --format=zip --output="$(basename "$PWD").zip" HEAD
This command generates a zip file of the current project, excluding the files and directories specified with the export-ignore
attribute.
Conclusion
Leveraging Git's archiving features through the .gitattributes
file allows for efficient and customized distribution of your projects. By specifying which files to exclude, you can create cleaner archives tailored to your deployment needs.
Creating Custom Zip Files with Git
2024-11-15
Managing project distributions often requires creating archives that exclude certain files or directories. Git offers a streamlined approach to this through the .gitattributes
file, enabling precise control over the
contents of your archives.
Why Use Git for Custom Archives?
Utilizing Git's archiving capabilities provides several advantages:
- Consistency: Ensures that archives are generated from a specific commit, maintaining version integrity.
- Automation: Facilitates the creation of scripts for automated deployment processes.
- Customization: Allows exclusion of unnecessary files, resulting in cleaner and more efficient distributions.
Understanding the .gitattributes
Configuration
The .gitattributes
file defines how Git handles various files within your repository. Below is an example configuration:
Let's break down this configuration:
- General Settings:
* text=auto eol=lf
: Normalizes line endings to LF, ensuring consistency across different operating systems.*.php diff=php
: Specifies that PHP files should use PHP-specific diff settings.
- Export Ignoring: The
export-ignore
attribute tells Git to exclude specified files or directories from the archive:.editorconfig
,.gitattributes
,.gitignore
: Configuration files not needed in the distribution./.git
,/.idea
: Version control and IDE-specific directories./tests
,/util
,/vendor
: Directories containing tests, utilities, and dependencies that may not be necessary for the end-user.phpunit.xml
: Configuration file for PHPUnit, typically not required in the final product.
Creating the Archive
With the .gitattributes
file configured, you can create a zip archive using the following command:
git archive --worktree-attributes --format=zip --output="$(basename "$PWD").zip" HEAD
This command generates a zip file of the current project, excluding the files and directories specified with the export-ignore
attribute.
Conclusion
Leveraging Git's archiving features through the .gitattributes
file allows for efficient and customized distribution of your projects. By specifying which files to exclude, you can create cleaner archives tailored to your deployment needs.
Mammouth AI: One Subscription to Rule Them All?
2024-09-24
Introduction
I’ve recently become interested in the world of artificial intelligence. Having worked with several AI tools and purchased subscriptions to ChatGPT and MidJourney, I was intrigued when I started seeing ads for Mammouth AI — a platform that promises access to multiple top-tier AI models for a low monthly fee.
However, with little publicly available information and no free tier options, I was hesitant to dive in without knowing more. But for the sake of my YouTube and blog audience, I decided to take the plunge, sign up, and share my experience. Here’s what I found.
What Is Mammouth AI?
Mammouth AI positions itself as a one-stop platform for accessing various large language models (LLMs) and image generators. The homepage makes some bold claims, offering access to industry leaders like GPT-4, Claude AI, and MidJourney for just €10 a month. The platform promises a user-friendly interface that simplifies the use of these models, even for those who may not be particularly tech-savvy.
Signing Up and Getting Started
The sign-up process was straightforward and required committing to a monthly subscription. I decided to go ahead and choose the Starter plan at €10/month to give you all a firsthand look at what Mammouth AI has to offer.
First Impressions
Upon logging in, I found a clean, straightforward interface that indeed seems designed for ease of use. Mammouth AI offers features like one-click reprompting, where you can easily transfer a prompt from one model to another to see different outputs.
Exploring Features
- One/click Reprompting: I tested this by generating a Halloween-themed blog post across several models, including Llama, Claude AI, and Mistral. The process was seamless, and the reprompting feature worked well, providing varied results as expected.
- Multilingual Capabilities: Since I’m learning Portuguese, I was curious about the platform’s ability to translate content. I had the Mistral model translate the Halloween blog post into European Portuguese, and the result looked good and well-formatted. The translation feature is likely a function of the models themselves rather than a unique offering from Mammouth AI.
- File Upload and Data Analysis: I attempted to upload a CSV file for analysis, expecting to create a graph. Unfortunately, the platform doesn’t seem to support CSV uploads directly, forcing me to convert the file to a PDF. Even then, none of the models were able to generate the requested graph, with ChatGPT 4o providing Python scripts instead of a visual output. LLMs like ChatGPT can typically read text files natively, but this is restricted through the Mammouth AI interface. I have created graphs in ChatGPT numerous times, so I suspect that this limitation is do to how Mammouth AI in communicating with ChatGPT on the backend.
- Image and File Upload: I also tested the platform’s ability to analyze images by uploading a photo of our dog. The models offered reasonable guesses about her breed, with detailed descriptions that matched her appearance. This feature seemed more polished than the file upload and analysis tool.
- Image Generation: I tested the image generation capabilities by asking the models to create a Halloween scene featuring our dog. I used models like Stable Diffusion, DALL-E, and FLUX. While the results varied, based on the variations between the models themselves, each captured different elements of the prompt with varying degrees of success. However, MidJourney’s integration seemed to struggle, and I was unable to successfully generate an image while recording the video. Edit: I was able to generate an image with MidJourney after recording the video.
- Chat History: The history bar on the left of the interface is welcome, but is very basic with chat naming based on the first few words of the prompt, and delete as the only option. Noted improvements would include renaming capability and a warning before permanently deleting a chat.
Areas for Improvement
While Mammouth AI shows promise, there are a few areas where it could improve:
- No Free Tier: The absence of a free trial makes it difficult for users to test the platform without committing to a subscription.
- Limited File Support: The platform’s inability to handle common file types like CSV directly is a significant drawback, especially for users looking to perform data analysis.
- Unstable Integrations: Some integrations, like MidJourney, were unresponsive during my tests, indicating that the platform might still need some refinement.
- Interface: History renaming and delete warnings would be easy and helpful improvements to that feature. Native platforms offer a richer experience including image generation controls and special code display boxes that are not available in Mammouth AI.
Final Thoughts
Mammouth AI offers a compelling proposition: access to multiple AI models for a single, affordable subscription. For newbies, AI enthusiasts, and developers who want to experiment with various LLMs without juggling multiple subscriptions, this could be a valuable tool. However, the platform’s limitations mean that it may not yet be the perfect solution for everyone.
I’m still on the fence about committing to Mammouth AI myself, but I plan to continue experimenting with the platform, especially once the promised web search feature becomes available. I’ll be sure to share my findings in a follow-up video or blog post.
Conclusion
Thank you for joining me on this first exploration of Mammouth AI. If you found this review helpful, be sure to check out my YouTube channel, where I’ll continue to explore AI tools, programming tips, and more. If you’d like to support my work, you can visit ThreeLeaf.com and check out the affiliate links on my page.
Mammouth AI: One Subscription to Rule Them All?
2024-09-24
Introduction
I’ve recently become interested in the world of artificial intelligence. Having worked with several AI tools and purchased subscriptions to ChatGPT and MidJourney, I was intrigued when I started seeing ads for Mammouth AI — a platform that promises access to multiple top-tier AI models for a low monthly fee.
However, with little publicly available information and no free tier options, I was hesitant to dive in without knowing more. But for the sake of my YouTube and blog audience, I decided to take the plunge, sign up, and share my experience. Here’s what I found.
What Is Mammouth AI?
Mammouth AI positions itself as a one-stop platform for accessing various large language models (LLMs) and image generators. The homepage makes some bold claims, offering access to industry leaders like GPT-4, Claude AI, and MidJourney for just €10 a month. The platform promises a user-friendly interface that simplifies the use of these models, even for those who may not be particularly tech-savvy.
Signing Up and Getting Started
The sign-up process was straightforward and required committing to a monthly subscription. I decided to go ahead and choose the Starter plan at €10/month to give you all a firsthand look at what Mammouth AI has to offer.
First Impressions
Upon logging in, I found a clean, straightforward interface that indeed seems designed for ease of use. Mammouth AI offers features like one-click reprompting, where you can easily transfer a prompt from one model to another to see different outputs.
Exploring Features
- One/click Reprompting: I tested this by generating a Halloween-themed blog post across several models, including Llama, Claude AI, and Mistral. The process was seamless, and the reprompting feature worked well, providing varied results as expected.
- Multilingual Capabilities: Since I’m learning Portuguese, I was curious about the platform’s ability to translate content. I had the Mistral model translate the Halloween blog post into European Portuguese, and the result looked good and well-formatted. The translation feature is likely a function of the models themselves rather than a unique offering from Mammouth AI.
- File Upload and Data Analysis: I attempted to upload a CSV file for analysis, expecting to create a graph. Unfortunately, the platform doesn’t seem to support CSV uploads directly, forcing me to convert the file to a PDF. Even then, none of the models were able to generate the requested graph, with ChatGPT 4o providing Python scripts instead of a visual output. LLMs like ChatGPT can typically read text files natively, but this is restricted through the Mammouth AI interface. I have created graphs in ChatGPT numerous times, so I suspect that this limitation is do to how Mammouth AI in communicating with ChatGPT on the backend.
- Image and File Upload: I also tested the platform’s ability to analyze images by uploading a photo of our dog. The models offered reasonable guesses about her breed, with detailed descriptions that matched her appearance. This feature seemed more polished than the file upload and analysis tool.
- Image Generation: I tested the image generation capabilities by asking the models to create a Halloween scene featuring our dog. I used models like Stable Diffusion, DALL-E, and FLUX. While the results varied, based on the variations between the models themselves, each captured different elements of the prompt with varying degrees of success. However, MidJourney’s integration seemed to struggle, and I was unable to successfully generate an image while recording the video. Edit: I was able to generate an image with MidJourney after recording the video.
- Chat History: The history bar on the left of the interface is welcome, but is very basic with chat naming based on the first few words of the prompt, and delete as the only option. Noted improvements would include renaming capability and a warning before permanently deleting a chat.
Areas for Improvement
While Mammouth AI shows promise, there are a few areas where it could improve:
- No Free Tier: The absence of a free trial makes it difficult for users to test the platform without committing to a subscription.
- Limited File Support: The platform’s inability to handle common file types like CSV directly is a significant drawback, especially for users looking to perform data analysis.
- Unstable Integrations: Some integrations, like MidJourney, were unresponsive during my tests, indicating that the platform might still need some refinement.
- Interface: History renaming and delete warnings would be easy and helpful improvements to that feature. Native platforms offer a richer experience including image generation controls and special code display boxes that are not available in Mammouth AI.
Final Thoughts
Mammouth AI offers a compelling proposition: access to multiple AI models for a single, affordable subscription. For newbies, AI enthusiasts, and developers who want to experiment with various LLMs without juggling multiple subscriptions, this could be a valuable tool. However, the platform’s limitations mean that it may not yet be the perfect solution for everyone.
I’m still on the fence about committing to Mammouth AI myself, but I plan to continue experimenting with the platform, especially once the promised web search feature becomes available. I’ll be sure to share my findings in a follow-up video or blog post.
Conclusion
Thank you for joining me on this first exploration of Mammouth AI. If you found this review helpful, be sure to check out my YouTube channel, where I’ll continue to explore AI tools, programming tips, and more. If you’d like to support my work, you can visit ThreeLeaf.com and check out the affiliate links on my page.
Building a Core PHP Single-Page CRUD API
2024-08-29
This blog post demonstrates how to create a simple single-page CRUD (Create, Read, Update, Delete) API using only core PHP. The purpose of this project is to provide a clear and straightforward example for beginners who want to understand how to develop a RESTful API in PHP without using frameworks.
System Requirements
To run this project, ensure you have the following installed:
- PHP: This example uses the latest PHP features and syntax available in version 8.2.
- MariaDB: The example requires a MariaDB or MySQL instance for the database operations.
- cURL: A command-line tool for sending HTTP requests. Note that any REST client can be used to test the API endpoints.
- Composer: The
composer.json
file is included to provide context for PHP server requirements and dependencies, but it is optional for running this project.
Setup Instructions
- Create the
api.php
file: Place theapi.php
file on any machine that has PHP configured. - Configure Database Connection: Ensure the database connection settings in the
api.php
file are correctly set for your database instance. This will allow the API to connect to the database and perform necessary operations. - Run the PHP Server: Start the PHP built-in server in the same folder as
api.php
to use the example REST calls as-is:
php -S localhost:8080
The Code
<?php
/*
* CRUD API Example in core PHP.
*
* This script provides a basic implementation of a CRUD (Create, Read, Update, Delete) API
* using PHP, PDO for database interactions, and JSON for data exchange. The API supports
* creating, reading, updating, and deleting customer records in a MySQL database.
* It also includes endpoints for setting up and tearing down the customers table, which are
* available for illustrative purposes and convenience in this example, but should never be
* placed in a production environment.
*/
$host = '127.0.0.1';
$dbname = 'testing';
$username = 'username';
$password = 'password';
try {
/* Establish database connection */
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
die('Database connection failed: ' . $exception->getMessage());
}
/**
* Validate user input data for creating or updating a customer.
*
* @param string[] $data The data to validate
*
* @return string[] The array of validation errors
*/
function validateInput(array $data): array
{
$errors = [];
if (empty($data['firstName'])) {
$errors[] = 'First name is required';
}
if (empty($data['lastName'])) {
$errors[] = 'Last name is required';
}
if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Valid email is required';
}
return $errors;
}
/* Retrieve HTTP method and path segments */
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($path, '/'));
/* Determine the resource and customer ID (if any) from the URL */
$resource = $segments[1] ?? '';
$customerId = $segments[2] ?? '';
switch ($resource) {
case 'up':
if ($method === 'POST') {
try {
/* Create 'customers' table if it doesn't exist */
$sql = 'CREATE TABLE IF NOT EXISTS customers (
customerId CHAR(36) DEFAULT uuid() PRIMARY KEY,
firstName VARCHAR(255) NOT NULL,
lastName VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)';
$pdo->exec($sql);
echo json_encode(['message' => 'Table created successfully']);
} catch (PDOException $exception) {
http_response_code(500);
echo json_encode(['error' => 'Failed to create table: ' . $exception->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'down':
if ($method === 'DELETE') {
try {
/* Drop 'customers' table if it exists */
$sql = 'DROP TABLE IF EXISTS customers';
$pdo->exec($sql);
echo json_encode(['message' => 'Table dropped successfully']);
} catch (PDOException $exception) {
http_response_code(500);
echo json_encode(['error' => 'Failed to drop table: ' . $exception->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'customers':
switch ($method) {
case 'GET':
if ($customerId) {
/* Read one customer */
$stmt = $pdo->prepare('SELECT * FROM customers WHERE customerId = :customerId');
$stmt->execute(['customerId' => $customerId]);
$customer = $stmt->fetch(PDO::FETCH_ASSOC);
if ($customer) {
echo json_encode($customer);
} else {
http_response_code(404);
echo json_encode(['error' => 'Customer not found']);
}
} else {
/* Read all customers */
$stmt = $pdo->query('SELECT * FROM customers');
$customers = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($customers);
}
break;
case 'POST':
/* Create a new customer */
$data = json_decode(file_get_contents('php://input'), true);
$errors = validateInput($data);
if (empty($errors)) {
$stmt = $pdo->prepare('INSERT INTO customers (firstName, lastName, email) VALUES (:firstName, :lastName, :email)');
$stmt->execute([
'firstName' => $data['firstName'],
'lastName' => $data['lastName'],
'email' => $data['email']
]);
/* Fetch the customerId of the newly created customer */
$stmt = $pdo->query('SELECT customerId FROM customers ORDER BY created_at DESC LIMIT 1');
$customerId = $stmt->fetchColumn();
echo json_encode(['message' => 'Customer created successfully', 'customerId' => $customerId]);
} else {
http_response_code(400);
echo json_encode(['errors' => $errors]);
}
break;
case 'PUT':
if ($customerId) {
/* Update an existing customer */
$stmt = $pdo->prepare('SELECT COUNT(*) FROM customers WHERE customerId = :customerId');
$stmt->execute(['customerId' => $customerId]);
$customerExists = $stmt->fetchColumn();
if ($customerExists) {
$data = json_decode(file_get_contents('php://input'), true);
$errors = validateInput($data);
if (empty($errors)) {
$stmt = $pdo->prepare('UPDATE customers SET firstName = :firstName, lastName = :lastName, email = :email WHERE customerId = :customerId');
$stmt->execute([
'firstName' => $data['firstName'],
'lastName' => $data['lastName'],
'email' => $data['email'],
'customerId' => $customerId
]);
echo json_encode(['message' => 'Customer updated successfully']);
} else {
http_response_code(400);
echo json_encode(['errors' => $errors]);
}
} else {
http_response_code(404);
echo json_encode(['error' => 'Customer not found']);
}
} else {
http_response_code(400);
echo json_encode(['error' => 'Customer ID is required']);
}
break;
case 'DELETE':
if ($customerId) {
/* Delete an existing customer */
$stmt = $pdo->prepare('DELETE FROM customers WHERE customerId = :customerId');
$stmt->execute(['customerId' => $customerId]);
echo json_encode(['message' => 'Customer deleted successfully']);
} else {
http_response_code(400);
echo json_encode(['error' => 'Customer ID is required']);
}
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
break;
}
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Endpoint not found']);
break;
}
Operation Sequence Diagram
Below is a sequence diagram that shows the flow of API requests and interactions with the database.
API Endpoints
This API provides the following endpoints:
1. Bring the Application Up
- URL:
/api.php/up
- Method:
POST
- Description: Initializes the application and creates the
customers
table in the database.
curl --request POST \
--url http://localhost:8080/api.php/up \
--header 'Content-Type: application/json'
Example Output:
{
"message": "Table created successfully"
}
2. Create a New Customer
- URL:
/api.php/customers
- Method:
POST
- Description: Creates a new customer record.
curl --request POST \
--url http://localhost:8080/api.php/customers \
--header 'Content-Type: application/json' \
--data '{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
}'
Example Output:
{
"message": "Customer created successfully",
"customerId": "2090a31b-64aa-11ef-9cca-0242ac120002"
}
3. Update an Existing Customer
- URL:
/api.php/customers/{customerId}
- Method:
PUT
- Description: Updates an existing customer record by ID.
curl --request PUT \
--url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002 \
--header 'Content-Type: application/json' \
--data '{
"firstName": "Janet",
"lastName": "Smith",
"email": "jane.smith@example.com"
}'
Example Output:
{
"message": "Customer updated successfully"
}
4. Retrieve a Customer by ID
- URL:
/api.php/customers/{customerId}
- Method:
GET
- Description: Retrieves a customer record by ID.
curl --request GET \
--url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002
Example Output:
{
"customerId": "2090a31b-64aa-11ef-9cca-0242ac120002",
"firstName": "Janet",
"lastName": "Smith",
"email": "jane.smith@example.com",
"created_at": "2024-08-27 19:25:32"
}
5. Retrieve All Customers
- URL:
/api.php/customers
- Method:
GET
- Description: Retrieves all customer records.
curl --request GET \
--url http://localhost:8080/api.php/customers/
Example Output:
[
{
"customerId": "2090a31b-64aa-11ef-9cca-0242ac120002",
"firstName": "Janet",
"lastName": "Smith",
"email": "jane.smith@example.com",
"created_at": "2024-08-27 19:25:32"
},
{
"customerId": "9b9a1019-65c0-11ef-87f6-0242ac140002",
"firstName": "John",
"lastName": "Marsh",
"email": "john.marsh@example.com",
"created_at": "2024-08-29 04:38:59"
}
]
6. Delete a Customer
- URL:
/api.php/customers/{customerId}
- Method:
DELETE
- Description: Deletes a customer record by ID.
curl --request DELETE \
--url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002
Example Output:
{
"message": "Customer deleted successfully"
}
7. Bring the Application Down
- URL:
/api.php/down
- Method:
DELETE
- Description: Cleans up and destroys the
customers
table in the database.
curl --request DELETE \
--url http://localhost:8080/api.php/down \
--header 'Content-Type: application/json'
Example Output:
{
"message": "Table dropped successfully"
}
Conclusion
This example demonstrates a fundamental RESTful API using core PHP and MariaDB. It is an excellent starting point for beginners looking to understand the basics of API development without requiring a framework. You can use this code to build upon and expand your knowledge of RESTful services in PHP.
Building a Core PHP Single-Page CRUD API
2024-08-29
This blog post demonstrates how to create a simple single-page CRUD (Create, Read, Update, Delete) API using only core PHP. The purpose of this project is to provide a clear and straightforward example for beginners who want to understand how to develop a RESTful API in PHP without using frameworks.
System Requirements
To run this project, ensure you have the following installed:
- PHP: This example uses the latest PHP features and syntax available in version 8.2.
- MariaDB: The example requires a MariaDB or MySQL instance for the database operations.
- cURL: A command-line tool for sending HTTP requests. Note that any REST client can be used to test the API endpoints.
- Composer: The
composer.json
file is included to provide context for PHP server requirements and dependencies, but it is optional for running this project.
Setup Instructions
- Create the
api.php
file: Place theapi.php
file on any machine that has PHP configured. - Configure Database Connection: Ensure the database connection settings in the
api.php
file are correctly set for your database instance. This will allow the API to connect to the database and perform necessary operations. - Run the PHP Server: Start the PHP built-in server in the same folder as
api.php
to use the example REST calls as-is:
php -S localhost:8080
The Code
<?php
/*
* CRUD API Example in core PHP.
*
* This script provides a basic implementation of a CRUD (Create, Read, Update, Delete) API
* using PHP, PDO for database interactions, and JSON for data exchange. The API supports
* creating, reading, updating, and deleting customer records in a MySQL database.
* It also includes endpoints for setting up and tearing down the customers table, which are
* available for illustrative purposes and convenience in this example, but should never be
* placed in a production environment.
*/
$host = '127.0.0.1';
$dbname = 'testing';
$username = 'username';
$password = 'password';
try {
/* Establish database connection */
$pdo = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
die('Database connection failed: ' . $exception->getMessage());
}
/**
* Validate user input data for creating or updating a customer.
*
* @param string[] $data The data to validate
*
* @return string[] The array of validation errors
*/
function validateInput(array $data): array
{
$errors = [];
if (empty($data['firstName'])) {
$errors[] = 'First name is required';
}
if (empty($data['lastName'])) {
$errors[] = 'Last name is required';
}
if (empty($data['email']) || !filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Valid email is required';
}
return $errors;
}
/* Retrieve HTTP method and path segments */
$method = $_SERVER['REQUEST_METHOD'];
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = explode('/', trim($path, '/'));
/* Determine the resource and customer ID (if any) from the URL */
$resource = $segments[1] ?? '';
$customerId = $segments[2] ?? '';
switch ($resource) {
case 'up':
if ($method === 'POST') {
try {
/* Create 'customers' table if it doesn't exist */
$sql = 'CREATE TABLE IF NOT EXISTS customers (
customerId CHAR(36) DEFAULT uuid() PRIMARY KEY,
firstName VARCHAR(255) NOT NULL,
lastName VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)';
$pdo->exec($sql);
echo json_encode(['message' => 'Table created successfully']);
} catch (PDOException $exception) {
http_response_code(500);
echo json_encode(['error' => 'Failed to create table: ' . $exception->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'down':
if ($method === 'DELETE') {
try {
/* Drop 'customers' table if it exists */
$sql = 'DROP TABLE IF EXISTS customers';
$pdo->exec($sql);
echo json_encode(['message' => 'Table dropped successfully']);
} catch (PDOException $exception) {
http_response_code(500);
echo json_encode(['error' => 'Failed to drop table: ' . $exception->getMessage()]);
}
} else {
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
}
break;
case 'customers':
switch ($method) {
case 'GET':
if ($customerId) {
/* Read one customer */
$stmt = $pdo->prepare('SELECT * FROM customers WHERE customerId = :customerId');
$stmt->execute(['customerId' => $customerId]);
$customer = $stmt->fetch(PDO::FETCH_ASSOC);
if ($customer) {
echo json_encode($customer);
} else {
http_response_code(404);
echo json_encode(['error' => 'Customer not found']);
}
} else {
/* Read all customers */
$stmt = $pdo->query('SELECT * FROM customers');
$customers = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($customers);
}
break;
case 'POST':
/* Create a new customer */
$data = json_decode(file_get_contents('php://input'), true);
$errors = validateInput($data);
if (empty($errors)) {
$stmt = $pdo->prepare('INSERT INTO customers (firstName, lastName, email) VALUES (:firstName, :lastName, :email)');
$stmt->execute([
'firstName' => $data['firstName'],
'lastName' => $data['lastName'],
'email' => $data['email']
]);
/* Fetch the customerId of the newly created customer */
$stmt = $pdo->query('SELECT customerId FROM customers ORDER BY created_at DESC LIMIT 1');
$customerId = $stmt->fetchColumn();
echo json_encode(['message' => 'Customer created successfully', 'customerId' => $customerId]);
} else {
http_response_code(400);
echo json_encode(['errors' => $errors]);
}
break;
case 'PUT':
if ($customerId) {
/* Update an existing customer */
$stmt = $pdo->prepare('SELECT COUNT(*) FROM customers WHERE customerId = :customerId');
$stmt->execute(['customerId' => $customerId]);
$customerExists = $stmt->fetchColumn();
if ($customerExists) {
$data = json_decode(file_get_contents('php://input'), true);
$errors = validateInput($data);
if (empty($errors)) {
$stmt = $pdo->prepare('UPDATE customers SET firstName = :firstName, lastName = :lastName, email = :email WHERE customerId = :customerId');
$stmt->execute([
'firstName' => $data['firstName'],
'lastName' => $data['lastName'],
'email' => $data['email'],
'customerId' => $customerId
]);
echo json_encode(['message' => 'Customer updated successfully']);
} else {
http_response_code(400);
echo json_encode(['errors' => $errors]);
}
} else {
http_response_code(404);
echo json_encode(['error' => 'Customer not found']);
}
} else {
http_response_code(400);
echo json_encode(['error' => 'Customer ID is required']);
}
break;
case 'DELETE':
if ($customerId) {
/* Delete an existing customer */
$stmt = $pdo->prepare('DELETE FROM customers WHERE customerId = :customerId');
$stmt->execute(['customerId' => $customerId]);
echo json_encode(['message' => 'Customer deleted successfully']);
} else {
http_response_code(400);
echo json_encode(['error' => 'Customer ID is required']);
}
break;
default:
http_response_code(405);
echo json_encode(['error' => 'Method not allowed']);
break;
}
break;
default:
http_response_code(404);
echo json_encode(['error' => 'Endpoint not found']);
break;
}
Operation Sequence Diagram
Below is a sequence diagram that shows the flow of API requests and interactions with the database.
API Endpoints
This API provides the following endpoints:
1. Bring the Application Up
- URL:
/api.php/up
- Method:
POST
- Description: Initializes the application and creates the
customers
table in the database.
curl --request POST \
--url http://localhost:8080/api.php/up \
--header 'Content-Type: application/json'
Example Output:
{
"message": "Table created successfully"
}
2. Create a New Customer
- URL:
/api.php/customers
- Method:
POST
- Description: Creates a new customer record.
curl --request POST \
--url http://localhost:8080/api.php/customers \
--header 'Content-Type: application/json' \
--data '{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
}'
Example Output:
{
"message": "Customer created successfully",
"customerId": "2090a31b-64aa-11ef-9cca-0242ac120002"
}
3. Update an Existing Customer
- URL:
/api.php/customers/{customerId}
- Method:
PUT
- Description: Updates an existing customer record by ID.
curl --request PUT \
--url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002 \
--header 'Content-Type: application/json' \
--data '{
"firstName": "Janet",
"lastName": "Smith",
"email": "jane.smith@example.com"
}'
Example Output:
{
"message": "Customer updated successfully"
}
4. Retrieve a Customer by ID
- URL:
/api.php/customers/{customerId}
- Method:
GET
- Description: Retrieves a customer record by ID.
curl --request GET \
--url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002
Example Output:
{
"customerId": "2090a31b-64aa-11ef-9cca-0242ac120002",
"firstName": "Janet",
"lastName": "Smith",
"email": "jane.smith@example.com",
"created_at": "2024-08-27 19:25:32"
}
5. Retrieve All Customers
- URL:
/api.php/customers
- Method:
GET
- Description: Retrieves all customer records.
curl --request GET \
--url http://localhost:8080/api.php/customers/
Example Output:
[
{
"customerId": "2090a31b-64aa-11ef-9cca-0242ac120002",
"firstName": "Janet",
"lastName": "Smith",
"email": "jane.smith@example.com",
"created_at": "2024-08-27 19:25:32"
},
{
"customerId": "9b9a1019-65c0-11ef-87f6-0242ac140002",
"firstName": "John",
"lastName": "Marsh",
"email": "john.marsh@example.com",
"created_at": "2024-08-29 04:38:59"
}
]
6. Delete a Customer
- URL:
/api.php/customers/{customerId}
- Method:
DELETE
- Description: Deletes a customer record by ID.
curl --request DELETE \
--url http://localhost:8080/api.php/customers/2090a31b-64aa-11ef-9cca-0242ac120002
Example Output:
{
"message": "Customer deleted successfully"
}
7. Bring the Application Down
- URL:
/api.php/down
- Method:
DELETE
- Description: Cleans up and destroys the
customers
table in the database.
curl --request DELETE \
--url http://localhost:8080/api.php/down \
--header 'Content-Type: application/json'
Example Output:
{
"message": "Table dropped successfully"
}
Conclusion
This example demonstrates a fundamental RESTful API using core PHP and MariaDB. It is an excellent starting point for beginners looking to understand the basics of API development without requiring a framework. You can use this code to build upon and expand your knowledge of RESTful services in PHP.