Module 1: Setting Up the Environment
In this module, we will set up the development environment for our Supply Chain Management (SCM) system using Laravel 11. Laravel is a robust PHP framework that provides a structured way of building web applications. We will also set up a SQL database for our application.
Step 1: Installing Laravel
Install Composer: Composer is a dependency manager for PHP. If you don’t have it installed, you can download it from getcomposer.org.
Create a New Laravel Project: Open your terminal and run the following command to create a new Laravel project:
composer create-project --prefer-dist laravel/laravel scm-backend
Navigate to the Project Directory:
cd scm-backend
Step 2: Setting Up the Database
Create a SQL Database: Create a new database in your preferred SQL database management system (e.g., MySQL, PostgreSQL). For this example, we will use MySQL.
Configure the Database: Open the
.envfile in your Laravel project and update the database configuration settings to match your database credentials.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=scm
DB_USERNAME=root
DB_PASSWORD=password
Step 3: Running the Laravel Development Server
Start the Laravel Development Server: Run the following command to start the development server:
php artisan serve
The application will be accessible at
http://127.0.0.1:8000.
Step 4: Connecting to GitHub via Visual Studio Code
Initialize Git Repository: Open your terminal and navigate to your project directory. Then run the following commands:
git init
git add .
git commit -m "Initial commit"
Create GitHub Repository: Go to GitHub and create a new repository. Follow the instructions to push your local repository to GitHub.
Connect via Visual Studio Code:
- Open Visual Studio Code.
- Install the GitHub extension if not already installed.
- Clone the repository using the command palette (
Ctrl+Shift+P).
Step 5: Creating the Application Schema/Diagram
Before diving into code, it’s essential to have a clear architecture in mind. Here’s a high-level overview of our SCM API structure:
Entities and Relationships:
- Users: Handle authentication and roles (admin, employee, customer).
- Suppliers: Manage supplier information.
- Products: Track products and inventory.
- Orders: Manage customer orders.
- Shipments: Track shipment details.
- Employees: Handle employee details.
Application Flow:
- Authentication: User registration and login.
- CRUD Operations: Create, Read, Update, Delete for each entity.
- Advanced Filtering: Filter customers, orders, products, etc.
- Error Handling: Centralized error handling mechanism.
In this first module, we’ve set up the development environment for our Supply Chain Management REST API using Laravel. We’ve installed Laravel, set up a SQL database, created a basic server, and sketched out our application architecture. In the next module, we will design the database schema and define relationships between our entities.
Module 2: Database Design
In this module, we will design the database schema for our Supply Chain Management (SCM) system using SQL. We will define the necessary tables and relationships based on the entities and attributes outlined previously.
Step 1: Creating the Database Schema
We will define the following tables and their relationships:
Users
id: Primary Keyusername: Unique identifier for each useremail: Unique email for each userpassword: Password for user authenticationrole: User role (admin, employee, customer)timestamps: Created at and updated at timestamps
Suppliers
id: Primary Keyname: Name of the supplieraddress: Address of the suppliercontact_person: Name of the contact person at the supplierphone_number: Phone number of the suppliertimestamps: Created at and updated at timestamps
Products
id: Primary Keyname: Name of the productdescription: Description of the productunit_price: Price per unit of the productquantity_available: Quantity of the product available in inventorysupplier_id: Foreign Key referencingsupplierstimestamps: Created at and updated at timestamps
Orders
id: Primary Keyproduct_id: Foreign Key referencingproductssupplier_id: Foreign Key referencingsuppliersorder_date: Date when the order was placedquantity_ordered: Quantity of the product orderedtimestamps: Created at and updated at timestamps
Shipments
id: Primary Keyorder_id: Foreign Key referencingordersshipment_date: Date when the shipment was sentestimated_arrival_date: Estimated arrival date of the shipmentactual_arrival_date: Actual arrival date of the shipmenttimestamps: Created at and updated at timestamps
Step 2: Creating Migrations in Laravel
Laravel provides a powerful migration system that allows us to create and manage our database schema. We will create migrations for each of the tables defined above.
- Create Users Migration:
php artisan make:migration create_users_table
Edit the migration file to include the columns for the users table:
// database/migrations/xxxx_xx_xx_xxxxxx_create_users_table.php
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('username')->unique();
$table->string('email')->unique();
$table->string('password');
$table->enum('role', ['admin', 'employee', 'customer'])->default('customer');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('users');
}
This code is a database migration file. In Laravel (a PHP framework), migrations are used to manage database schema changes over time, making it easier to keep track of database changes across different development environments (like local development, staging, and production).
Explanation of the Code:
File Location and Name:
- This file is typically found in the
database/migrationsdirectory of a Laravel project. Its name follows a timestamp convention (xxxx_xx_xx_xxxxxx_create_users_table.php) to ensure it runs in the correct order.
- This file is typically found in the
up()Method:- This method is used to define the actions that should be performed when the migration is applied (run). Here’s what it does:
- It uses the
Schemaclass to create a new table namedusers. - Inside the
Schema::createmethod, aBlueprintobject ($table) is used to define the structure of theuserstable. id(): Adds an auto-incrementing primary key column namedid.string('username')->unique(): Adds ausernamecolumn of type string (varchar) that must be unique across all records in the table.string('email')->unique(): Adds anemailcolumn of type string (varchar) that must be unique across all records in the table.string('password'): Adds apasswordcolumn of type string (varchar) to store hashed passwords securely.enum('role', ['admin', 'employee', 'customer'])->default('customer'): Adds arolecolumn of type enum, which restricts its value to one of the specified options (admin,employee,customer). The default value is set tocustomer.timestamps(): Adds two columns,created_atandupdated_at, which automatically record the creation and last update timestamps of each record.
- It uses the
- This method is used to define the actions that should be performed when the migration is applied (run). Here’s what it does:
down()Method:- This method specifies the actions to be performed when the migration is rolled back (reversed).
Schema::dropIfExists('users'): Deletes theuserstable from the database if it exists, effectively undoing what was done in theup()method.
In essence, this migration file sets up a users table in the database with columns for id, username, email, password, role, created_at, and updated_at. The up() method creates the table with specified columns and constraints, while the down() method allows for dropping (deleting) the table if needed, ensuring the database schema remains consistent and manageable throughout the application’s lifecycle.
Create Suppliers Migration:
php artisan make:migration create_suppliers_table
Edit the migration file to include the columns for the suppliers table:
// database/migrations/xxxx_xx_xx_xxxxxx_create_suppliers_table.php
public function up()
{
Schema::create('suppliers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('address');
$table->string('contact_person');
$table->string('phone_number');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('suppliers');
}
This migration file (xxxx_xx_xx_xxxxxx_create_suppliers_table.php) is used to define and manage the structure of the suppliers table in a Laravel application’s database.
Explanation of the Code:
File Location and Name:
- This file is located in the
database/migrationsdirectory of a Laravel project. - Its name follows the timestamp convention (
xxxx_xx_xx_xxxxxx_create_suppliers_table.php), ensuring proper execution order when running migrations.
- This file is located in the
up()Method:- This method defines the actions that should be performed when the migration is applied (run). Here’s what it does:
- Uses the
Schemaclass to create a new table namedsuppliers. - Inside the
Schema::createmethod, aBlueprintobject ($table) is used to define the structure of thesupplierstable. id(): Adds an auto-incrementing primary key column namedid.string('name'): Adds anamecolumn of type string (varchar) to store the name of the supplier.string('address'): Adds anaddresscolumn of type string (varchar) to store the supplier’s address.string('contact_person'): Adds acontact_personcolumn of type string (varchar) to store the name of the contact person at the supplier.string('phone_number'): Adds aphone_numbercolumn of type string (varchar) to store the supplier’s phone number.timestamps(): Adds two columns,created_atandupdated_at, which automatically record the creation and last update timestamps of each record.
- Uses the
- This method defines the actions that should be performed when the migration is applied (run). Here’s what it does:
down()Method:- This method specifies the actions to be performed when the migration is rolled back (reversed).
Schema::dropIfExists('suppliers'): Deletes thesupplierstable from the database if it exists, effectively undoing what was done in theup()method.
Create Products Migration:
php artisan make:migration create_products_table
Edit the migration file to include the columns for the products table:
// database/migrations/xxxx_xx_xx_xxxxxx_create_products_table.php
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description');
$table->decimal('unit_price', 8, 2);
$table->integer('quantity_available');
$table->foreignId('supplier_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('products');
}
This migration file (xxxx_xx_xx_xxxxxx_create_products_table.php) is used to define and manage the structure of the products table in a Laravel application’s database.
Explanation of the Code:
File Location and Name:
- This file is located in the
database/migrationsdirectory of a Laravel project. - Its name follows the timestamp convention (
xxxx_xx_xx_xxxxxx_create_products_table.php), ensuring proper execution order when running migrations.
- This file is located in the
up()Method:- This method defines the actions that should be performed when the migration is applied (run). Here’s what it does:
- Uses the
Schemaclass to create a new table namedproducts. - Inside the
Schema::createmethod, aBlueprintobject ($table) is used to define the structure of theproductstable. id(): Adds an auto-incrementing primary key column namedid.string('name'): Adds anamecolumn of type string (varchar) to store the name of the product.text('description'): Adds adescriptioncolumn of type text to store a detailed description of the product.decimal('unit_price', 8, 2): Adds aunit_pricecolumn of type decimal to store the price of the product. The parameters8, 2specify that it can hold up to 8 digits in total with 2 digits after the decimal point.integer('quantity_available'): Adds aquantity_availablecolumn of type integer to store the number of units of the product available in stock.foreignId('supplier_id')->constrained()->onDelete('cascade'): Adds asupplier_idcolumn as a foreign key that references theidcolumn in thesupplierstable. Theconstrained()method ensures referential integrity (thesupplier_idmust exist in thesupplierstable). TheonDelete('cascade')option specifies that if a supplier is deleted, all associated products should also be deleted.timestamps(): Adds two columns,created_atandupdated_at, which automatically record the creation and last update timestamps of each record.
- Uses the
- This method defines the actions that should be performed when the migration is applied (run). Here’s what it does:
down()Method:- This method specifies the actions to be performed when the migration is rolled back (reversed).
Schema::dropIfExists('products'): Deletes theproductstable from the database if it exists, effectively undoing what was done in theup()method.
Summary
In summary, this migration file sets up a products table in the database with columns for id, name, description, unit_price, quantity_available, supplier_id, created_at, and updated_at. The up() method creates the table with specified columns and constraints, while the down() method allows for dropping (deleting) the table if needed, ensuring the database schema remains consistent and manageable throughout the application’s lifecycle. The use of foreign key constraints (supplier_id) helps establish relationships between products and their respective suppliers, promoting data integrity within the database.
Create Orders Migration:
php artisan make:migration create_orders_table
The command php artisan make:migration create_orders_table is used in Laravel to generate a new migration file specifically for creating a database table named orders. Here’s a breakdown of what this command does:
php artisan: This is the command-line interface (CLI) tool provided by Laravel for various development tasks.
make
: This part of the command instructs Laravel to create a new migration file.
create_orders_table: This is the name provided after
make:migration. Laravel will use this name to generate a timestamped migration file in thedatabase/migrationsdirectory. The file name will follow a format likexxxx_xx_xx_xxxxxx_create_orders_table.php, wherexxxx_xx_xx_xxxxxxis a timestamp that ensures migrations are run in the order they were created.
Purpose of the Migration File:
- Creating the
ordersTable: The generated migration file will contain methods (up()anddown()) where you can define the schema for theorderstable.
Typical Structure of the Migration File:
In the
up()method, you’ll define how theorderstable should be created, specifying columns likeid,user_id,total_amount,status,created_at, andupdated_at.In the
down()method, you’ll define how to drop (delete) theorderstable if needed, ensuring the database schema remains consistent during development, testing, and production.
Edit the migration file to include the columns for the orders table:
// database/migrations/xxxx_xx_xx_xxxxxx_create_orders_table.php
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->foreignId('supplier_id')->constrained()->onDelete('cascade');
$table->date('order_date');
$table->integer('quantity_ordered');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('orders');
}
This migration file is used to define and manage the structure of the orders table in a Laravel application’s database.
Explanation of the Code:
File Location and Name:
- This file is located in the
database/migrationsdirectory of a Laravel project. - Its name follows the timestamp convention (
xxxx_xx_xx_xxxxxx_create_orders_table.php), ensuring proper execution order when running migrations.
- This file is located in the
up()Method:- This method defines the actions that should be performed when the migration is applied (run). Here’s what it does:
- Uses the
Schemaclass to create a new table namedorders. - Inside the
Schema::createmethod, aBlueprintobject ($table) is used to define the structure of theorderstable. id(): Adds an auto-incrementing primary key column namedid.foreignId('product_id')->constrained()->onDelete('cascade'): Adds aproduct_idcolumn as a foreign key that references theidcolumn in theproductstable. Theconstrained()method ensures referential integrity (theproduct_idmust exist in theproductstable). TheonDelete('cascade')option specifies that if a product is deleted, all associated orders should also be deleted.foreignId('supplier_id')->constrained()->onDelete('cascade'): Adds asupplier_idcolumn as a foreign key that references theidcolumn in thesupplierstable. Similarly, it ensures that each order is associated with a valid supplier, and it also specifies cascading deletion for referential integrity.date('order_date'): Adds anorder_datecolumn of type date to store the date when the order was placed.integer('quantity_ordered'): Adds aquantity_orderedcolumn of type integer to store the number of units of the product ordered in that specific order.timestamps(): Adds two columns,created_atandupdated_at, which automatically record the creation and last update timestamps of each record.
- Uses the
- This method defines the actions that should be performed when the migration is applied (run). Here’s what it does:
down()Method:- This method specifies the actions to be performed when the migration is rolled back (reversed).
Schema::dropIfExists('orders'): Deletes theorderstable from the database if it exists, effectively undoing what was done in theup()method.
In summary, this migration file sets up an orders table in the database with columns for id, product_id, supplier_id, order_date, quantity_ordered, created_at, and updated_at. The up() method creates the table with specified columns and foreign key constraints, while the down() method allows for dropping (deleting) the table if needed, ensuring the database schema remains consistent and manageable throughout the application’s lifecycle. The use of foreign key constraints (product_id and supplier_id) helps maintain relational integrity between orders, products, and suppliers in the database.
Create Shipments Migration:
php artisan make:migration create_shipments_table
Edit the migration file to include the columns for the shipments table:
// database/migrations/xxxx_xx_xx_xxxxxx_create_shipments_table.php
public function up()
{
Schema::create('shipments', function (Blueprint $table) {
$table->id();
$table->foreignId('order_id')->constrained()->onDelete('cascade');
$table->date('shipment_date');
$table->date('estimated_arrival_date');
$table->date('actual_arrival_date')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('shipments');
}
Step 3: Running Migrations
Run the migrations to create the database schema:
php artisan migrate
In this module, we have designed the database schema for our Supply Chain Management system using SQL and Laravel’s migration system. We created tables for users, suppliers, products, orders, and shipments, and defined the relationships between them. In the next module, we will implement user authentication and authorization, including registration, login, and role-based access control.
Module 3: User Authentication and Authorization
In this module, we will implement user authentication and authorization for our Supply Chain Management (SCM) system using Laravel. This includes user registration and login with error validation, full authorization, and authentication using JWT (JSON Web Tokens). We will also add middleware and error handlers for enhanced security and better user experience.
Step 1: Install and Configure Laravel Sanctum
Laravel Sanctum provides a featherweight authentication system for SPAs (single page applications), mobile applications, and simple, token-based APIs. First, we need to install Sanctum.
Install Sanctum:
composer require laravel/sanctum
Publish Sanctum Configuration:
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
he command php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider" in Laravel is used to publish assets provided by a package, specifically those registered by the SanctumServiceProvider. Here’s a breakdown of what this command does:
Purpose
This command allows you to publish configuration files, views, migrations, and other assets provided by the Sanctum package (laravel/sanctum). Sanctum is a Laravel package that provides a simple and lightweight way to authenticate APIs using tokens.
Explanation of the Command:
php artisan: This is the command-line interface (CLI) tool provided by Laravel for various development tasks.
vendor
: This Laravel command is used to publish assets from vendor packages.
–provider=”Laravel\Sanctum\SanctumServiceProvider”: This option specifies the provider whose assets you want to publish. In this case, it’s
SanctumServiceProvider, which is responsible for registering Sanctum’s services and configurations.
What Happens When You Run php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider":
Publishing Assets: When you run this command, Laravel will search for the
SanctumServiceProviderand execute itspublishes()method. This method typically defines which assets should be published (like configuration files, migrations, etc.) and where they should be published to (usually in theconfig,database, orresourcesdirectories of your Laravel application).Interaction: After running this command, Laravel will display a list of assets that were published and where they were published to. You may be prompted to confirm or select specific assets to publish, depending on how the
publishes()method inSanctumServiceProvideris configured.
Why Use vendor:publish?
Customization: Publishing allows you to customize and modify package assets according to your application’s specific needs. For example, you might want to customize Sanctum’s configuration file (
config/sanctum.php) or modify its migration files (database/migrations) before running them.Updates: It’s also useful when updating packages, as new versions might introduce changes to configuration or migration files that you’ll want to integrate into your application.
Run Sanctum Migrations:
php artisan migrate
Add Sanctum’s middleware:
Add Sanctum’s middleware to your api middleware group within your app/Http/Kernel.php file:
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
This code snippet is configuring middleware for the api middleware group in a Laravel application. Let’s break down what each part does:
Middleware Explanation:
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class:- This middleware (
EnsureFrontendRequestsAreStateful) is provided by the Sanctum package (laravel/sanctum). It ensures that requests coming from your frontend (typically JavaScript applications or SPAs) are stateful. In other words, it allows Sanctum to authenticate requests and maintain session state across requests originating from the frontend.
- This middleware (
'throttle:api':- This is a Laravel built-in middleware (
throttle) that limits the number of requests that can be made to the API endpoints. The:apisuffix indicates that the throttling settings specified in yourapp/Http/Kernel.phpfile under thethrottlemiddleware group will be applied to these API requests.
- This is a Laravel built-in middleware (
\Illuminate\Routing\Middleware\SubstituteBindings::class:- This middleware (
SubstituteBindings) is also a built-in Laravel middleware. It substitutes route model bindings in the controller methods with actual model instances. This means that if your controller method expects a model instance based on a route parameter (e.g.,User $user), Laravel will automatically resolve and inject the correspondingUsermodel instance based on the route parameter.
- This middleware (
Purpose:
- The
'api'middleware group is typically used to define middleware that should be applied to API routes in your Laravel application. - In this configuration:
- Sanctum’s
EnsureFrontendRequestsAreStatefulmiddleware ensures that requests from your frontend application are properly authenticated and maintain session state. - The
throttle:apimiddleware limits the rate of API requests to prevent abuse or excessive load on your server. SubstituteBindingsmiddleware ensures that route model bindings are resolved and injected correctly into your API controller methods.
- Sanctum’s
Usage:
- You would typically define this middleware configuration in your
app/Http/Kernel.phpfile under the$middlewareGroupsproperty, specifically under the'api'middleware group. - This setup ensures that every API request passing through this middleware group is handled according to these middleware rules, providing security, rate limiting, and model binding resolution for your API endpoints.
Configure Auth Guards:
Ensure your config/auth.php configuration file contains a sanctum guard configuration:
'guards' => [
'sanctum' => [
'driver' => 'sanctum',
'provider' => 'users',
],
],
This configuration snippet defines a custom authentication guard named 'sanctum' in a Laravel application. Let’s explain each part:
Explanation:
'guards' => [...]:- In Laravel, guards define how users are authenticated for each request. They can be configured to use different drivers (like session-based authentication, token-based authentication, etc.) and providers (where user credentials are stored).
'sanctum' => [...]:'sanctum'is the name of the custom guard being defined. This guard will use Sanctum for authentication.
'driver' => 'sanctum':- Specifies that the guard uses the
sanctumdriver. This means authentication will be handled by Sanctum’s token-based authentication mechanism.
- Specifies that the guard uses the
'provider' => 'users':- Specifies the authentication provider for this guard. In this case,
'users'indicates that user credentials will be retrieved from theuserstable in the database.
- Specifies the authentication provider for this guard. In this case,
Purpose:
Sanctum: Laravel Sanctum is a lightweight package that provides token-based authentication for APIs. It issues API tokens that clients can use to authenticate subsequent requests.
Guard Configuration: By defining
'sanctum'as a guard and specifying'driver' => 'sanctum', Laravel knows to use Sanctum’s authentication mechanism for this guard. The'provider' => 'users'part indicates that user credentials (like username and password) are expected to be stored in theuserstable of the database.
Usage:
You would typically define this guard configuration in your
config/auth.phpfile under the'guards'array. This file allows you to configure multiple guards, each with its own driver and provider settings.Once configured, you can use the
auth()->guard('sanctum')method to access this guard in your application, authenticate users via tokens issued by Sanctum, and protect routes accordingly.
Step 2: Create User Model and Migration
We already have a users table from the previous module. Now, we need to ensure our User model uses the HasApiTokens trait provided by Sanctum.
Update User Model:
// app/Models/User.php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
protected $fillable = [
'username', 'email', 'password', 'role',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
}
Explanation:
Namespace:
namespace App\Models;: Defines the namespace where theUsermodel class resides. Namespaces help organize and group classes logically within a Laravel application.
Imports:
use Illuminate\Contracts\Auth\MustVerifyEmail;: Imports theMustVerifyEmailcontract. This indicates that theUsermodel can implement email verification features if desired.use Illuminate\Foundation\Auth\User as Authenticatable;: Imports Laravel’sAuthenticatableclass, which provides foundational authentication functionality for theUsermodel.use Illuminate\Notifications\Notifiable;: Imports theNotifiabletrait, which adds support for sending notifications to users.use Laravel\Sanctum\HasApiTokens;: Imports theHasApiTokenstrait from Laravel Sanctum. This trait allows theUsermodel to issue API tokens and interact with Sanctum’s token-based authentication features.
Class Definition:
class User extends Authenticatable: Defines theUserclass, extending Laravel’sAuthenticatableclass. This means theUsermodel inherits methods and properties for authentication and authorization fromAuthenticatable.
Traits:
use HasApiTokens, Notifiable;: Uses theHasApiTokensandNotifiabletraits in theUsermodel. Traits are reusable sets of methods that can be used in multiple classes.HasApiTokens: Provides methods to issue API tokens and manage token authentication for theUsermodel, integrating with Laravel Sanctum.Notifiable: Enables theUsermodel to send notifications using Laravel’s notification system.
Properties:
$fillable: Specifies which attributes of theUsermodel are mass assignable. Mass assignment allows you to assign an array of attributes to the model all at once.- In this case, attributes
'username','email','password', and'role'can be mass assigned.
- In this case, attributes
$hidden: Specifies which attributes should be hidden when the model is converted to an array or JSON. Typically used for sensitive data like passwords.- Here,
'password'and'remember_token'are hidden.
- Here,
$casts: Specifies the data types of specific attributes. Laravel will automatically convert these attributes to native PHP types when accessing them.'email_verified_at'is cast to adatetimetype, ensuring it is treated as aDateTimeobject.
Purpose:
Authentication and Authorization: The
Usermodel is central to handling user authentication and authorization within a Laravel application. By extendingAuthenticatableand usingHasApiTokens, it integrates with Laravel’s authentication system and provides API token management via Sanctum.Notifications: With the
Notifiabletrait, theUsermodel gains the ability to send notifications, such as email notifications for password resets or account activations.Data Management: The
$fillable,$hidden, and$castsproperties define how data is managed and presented, ensuring security (by hiding sensitive information) and accurate data representation (by casting types correctly).
Step 3: Create Authentication Controllers
We will create a controller to handle user registration and login.
Create AuthController:
php artisan make:controller AuthController
Implement Registration and Login Methods:
// app/Http/Controllers/AuthController.php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Laravel\Sanctum\HasApiTokens;
class AuthController extends Controller
{
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'username' => 'required|string|max:255|unique:users',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$user = User::create([
'username' => $request->username,
'email' => $request->email,
'password' => Hash::make($request->password),
'role' => $request->role ?? 'customer',
]);
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
public function login(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'required|string|email|max:255',
'password' => 'required|string|min:8',
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response()->json(['message' => 'Invalid credentials'], 401);
}
$token = $user->createToken('auth_token')->plainTextToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
public function logout(Request $request)
{
$request->user()->tokens()->delete();
return response()->json(['message' => 'Logged out successfully'], 200);
}
}
The AuthController handles user authentication-related actions such as registration, login, and logout in a Laravel application. It interacts with the User model and uses Laravel Sanctum for token-based authentication.
Explanation of Methods:
register(Request $request):- This method handles user registration.
- Validation: It first validates the incoming request data using Laravel’s
Validatorclass. It checks for required fields (username,email,password) and ensures uniqueness forusernameandemail. - If validation fails (
$validator->fails()), it returns a JSON response with validation errors and HTTP status code422(Unprocessable Entity). - User Creation: If validation passes, it creates a new
Userrecord usingUser::create(). The password is hashed usingHash::make()for security. Ifroleis not provided in the request, it defaults to'customer'. - Token Creation: After creating the user, it generates a new API token (
auth_token) for the user using Sanctum’screateToken()method. - Finally, it returns a JSON response with the generated
access_tokenandtoken_type(Bearer).
login(Request $request):- This method handles user login.
- Validation: It validates the incoming request data for
emailandpassword. - If validation fails (
$validator->fails()), it returns a JSON response with validation errors and HTTP status code422. - Authentication: It attempts to retrieve a user based on the provided
email. If a user is found and the provided password matches the hashed password stored in the database (Hash::check()), it generates a new API token (auth_token) for the user using Sanctum’screateToken()method. - If authentication fails (either user not found or password does not match), it returns a JSON response with an error message and HTTP status code
401(Unauthorized). - If authentication succeeds, it returns a JSON response with the generated
access_tokenandtoken_type(Bearer).
logout(Request $request):- This method handles user logout.
- It deletes all tokens associated with the authenticated user (
$request->user()) using Sanctum’stokens()->delete()method. - After deleting the tokens, it returns a JSON response with a success message indicating that the user has been logged out (
'Logged out successfully') and HTTP status code200(OK).
Usage:
- Ensure that Sanctum is properly configured in your Laravel application (
config/auth.php). - This controller assumes you have routes defined to handle registration (
POST /register), login (POST /login), and logout (POST /logout) requests. - You can customize the validation rules and responses based on your application’s requirements.
Step 4: Define Authentication Routes
Add routes for registration, login, and logout.
Update
routes/api.php:
use App\Http\Controllers\AuthController;
Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);
Route::post('logout', [AuthController::class, 'logout'])->middleware('auth:sanctum');
This Laravel route definition snippet shows how to map HTTP POST requests to specific methods in the AuthController class using Laravel’s routing system. Let’s break down each route and its purpose:
Route Definitions:
Route::post('register', [AuthController::class, 'register']);- This route defines a POST endpoint at
/register. - Purpose: It directs incoming POST requests to the
registermethod within theAuthControllerclass. This method is responsible for handling user registration.
- This route defines a POST endpoint at
Route::post('login', [AuthController::class, 'login']);- This route defines a POST endpoint at
/login. - Purpose: It directs incoming POST requests to the
loginmethod within theAuthControllerclass. This method is responsible for handling user login authentication.
- This route defines a POST endpoint at
Route::post('logout', [AuthController::class, 'logout'])->middleware('auth:sanctum');- This route defines a POST endpoint at
/logout. - Purpose: It directs incoming POST requests to the
logoutmethod within theAuthControllerclass. This method is responsible for logging out the authenticated user. - Middleware: The
->middleware('auth:sanctum')part specifies that the route requires authentication via the Sanctum guard (auth:sanctum). This means that users attempting to access the/logoutendpoint must first be authenticated and have a valid Sanctum token.
- This route defines a POST endpoint at
Usage Explanation:
Controller Binding:
[AuthController::class, 'methodName']syntax binds the route to a specific method (register,login,logout) defined within theAuthControllerclass.POST Method: All routes use the POST HTTP method, which is typical for actions that modify data (like creating or updating resources).
Middleware: The
auth:sanctummiddleware applied to the/logoutroute ensures that only authenticated users with a valid Sanctum token can access the route. This protects the logout functionality and ensures that only logged-in users can log out.
Step 5: Protecting Routes and Middleware
We will now create middleware to protect our routes based on roles.
Create Role Middleware:
php artisan make:middleware RoleMiddleware
The command php artisan make:middleware RoleMiddleware in Laravel is used to generate a new middleware class named RoleMiddleware. Let’s break down what this command does and its purpose:
Purpose:
Middleware in Laravel provides a convenient mechanism for filtering HTTP requests entering your application. This allows you to perform tasks like authentication, authorization, and request preprocessing. The RoleMiddleware created with php artisan make:middleware can be used to check if a user has a specific role before allowing access to certain routes or actions.
Explanation of the Command:
php artisan: This is the command-line interface (CLI) tool provided by Laravel for various development tasks.
make
: This Laravel command generates a new middleware class.
RoleMiddleware: The name
RoleMiddlewareis the identifier for the middleware class being created. By convention, middleware classes in Laravel are named with theMiddlewaresuffix to indicate their purpose.
Generated File Location:
- After running
php artisan make:middleware RoleMiddleware, Laravel creates a new PHP class file namedRoleMiddleware.php. - By default, this file is placed in the
app/Http/Middlewaredirectory of your Laravel application.
Purpose of RoleMiddleware:
- Authorization: You can implement logic inside
RoleMiddlewareto check if the authenticated user has a specific role. - Conditional Access Control: Middleware allows you to intercept HTTP requests before they reach your routes or controllers. This interception can include checking roles, permissions, or any other criteria necessary for access control.
- Reusability: Middleware classes are reusable across different routes or controllers, making them a powerful tool for applying cross-cutting concerns such as role-based access control.
Typical Usage:
- After creating
RoleMiddleware, you would register it in your application’s HTTP kernel (app/Http/Kernel.php) under the$routeMiddlewarearray. - Inside routes or controllers, you can then apply
RoleMiddlewareeither globally, to specific routes, or groups of routes using Laravel’s middleware handling mechanisms.
Implement Role Middleware:
// app/Http/Middleware/RoleMiddleware.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RoleMiddleware
{
public function handle(Request $request, Closure $next, $role)
{
if (!Auth::check() || Auth::user()->role != $role) {
return response()->json(['message' => 'Forbidden'], 403);
}
return $next($request);
}
}
This RoleMiddleware class in Laravel is designed to handle role-based authorization checks for incoming HTTP requests. Let’s break down its components and functionality:
Namespace and Imports:
- Namespace:
namespace App\Http\Middleware; - Imports:
use Closure;: Imports theClosureclass, which represents the next middleware closure or the controller action.use Illuminate\Http\Request;: Imports theRequestclass for handling HTTP request objects.use Illuminate\Support\Facades\Auth;: Imports theAuthfacade for accessing authentication services.
Class Definition:
- Class:
RoleMiddleware- This class is defined in the
App\Http\Middlewarenamespace and is responsible for implementing the role-based authorization logic.
- This class is defined in the
Method:
handle(Request $request, Closure $next, $role):- Parameters:
$request: Represents the incoming HTTP request.$next: Closure that passes control to the next middleware or the controller action.$role: Role name parameter that specifies the role required for access.
- Functionality:
- Authentication Check: Checks if the user is authenticated (
!Auth::check()) using Laravel’s authentication system (Authfacade). - Role Check: Verifies if the authenticated user’s role matches the specified
$roleparameter (Auth::user()->role != $role). - Response Handling: If the user is not authenticated or does not have the required role, it returns a JSON response with a
403HTTP status code (Forbidden). - Next Middleware: If authentication and role checks pass, it forwards the request to the next middleware or controller action (
$next($request)).
- Authentication Check: Checks if the user is authenticated (
- Parameters:
Usage:
- Registering Middleware: After creating
RoleMiddleware, you would register it in yourapp/Http/Kernel.phpfile under the$routeMiddlewarearray. - Applying Middleware: You can apply
RoleMiddlewareto specific routes or groups of routes in your Laravel application to enforce role-based access control.
Register Middleware:
Register the middleware in app/Http/Kernel.php:
protected $routeMiddleware = [
// other middleware
'role' => \App\Http\Middleware\RoleMiddleware::class,
];
In Laravel, the $routeMiddleware property in the app/Http/Kernel.php file defines middleware that can be applied to routes or route groups. Here’s how you would integrate your RoleMiddleware into the $routeMiddleware array:
Explanation:
$routeMiddlewareProperty:- This property is an associative array where keys represent middleware aliases (names you can use to refer to the middleware in your route definitions), and values are the fully qualified class names of the middleware classes.
Adding
roleMiddleware:- To add your
RoleMiddlewareclass to the$routeMiddlewarearray, you assign it a key'role'and specify the fully qualified class name\App\Http\Middleware\RoleMiddleware::class.
- To add your
Usage:
Once added to
$routeMiddleware, you can use the'role'alias as middleware in your routes or route groups. For example:Route::get('/admin/dashboard', function () { // Logic for admin dashboard })->middleware('auth:sanctum', 'role:admin');Here,
'role:admin'will refer to yourRoleMiddlewareclass, which checks if the authenticated user has theadminrole before allowing access to the/admin/dashboardroute.
Protect Routes:
Apply the middleware to routes to protect them based on roles. For example:
Route::middleware(['auth:sanctum', 'role:admin'])->group(function () {
Route::get('/admin-only-route', function () {
return response()->json(['message' => 'Welcome, Admin!'], 200);
});
});
Explanation:
Route::middleware([...])->group(function () { ... }):- This defines a group of routes that share the same middleware stack (
['auth:sanctum', 'role:admin']). - Middleware Stack: Routes within this group will first go through the
auth:sanctummiddleware to ensure the user is authenticated using Laravel Sanctum. Then, they will pass through therole:adminmiddleware, which checks if the authenticated user has theadminrole.
- This defines a group of routes that share the same middleware stack (
Route::get('/admin-only-route', function () { ... }):- Defines a GET route for
/admin-only-route. - Closure Function: The function inside handles the logic for this route. In this example, it returns a JSON response with a welcome message specifically for admins (
'Welcome, Admin!').
- Defines a GET route for
Middleware Details:
auth:sanctum: Ensures the user is authenticated via Sanctum’s token-based authentication system. If not authenticated, Laravel will handle it by returning an appropriate response (e.g., redirect to login or JSON response with an error message).role:admin: Checks if the authenticated user has theadminrole. This is implemented in yourRoleMiddlewareclass, where it verifies the user’s role against the required role (adminin this case). If the user does not have theadminrole, the middleware will return a403Forbidden response.
Usage:
- Middleware Group: Wrapping routes in
Route::middleware([...])->group(function () { ... })ensures that all routes inside the group share the same middleware requirements. - Role-Based Authorization: By applying
'role:admin'middleware, you restrict access to the/admin-only-routeto only those users who are authenticated via Sanctum and have theadminrole.
Example Result:
- When an authenticated user with the
adminrole accesses/admin-only-route, they will receive the JSON response'Welcome, Admin!'. - If an authenticated user without the
adminrole tries to access the route, therole:adminmiddleware will block access and return a403Forbidden response.
In this module, we implemented user authentication and authorization for our Supply Chain Management system using Laravel Sanctum. We created endpoints for user registration, login, and logout, and protected routes using middleware. In the next module, we will create the models for other entities in the SCM system.
Module 4: Models Creation
In this module, we will create the models for the key entities in our Supply Chain Management (SCM) system using Laravel. These entities include Supplier, Product, Order, and Shipment. We will also define relationships between these entities.
Step 1: Creating Models and Migrations
We will use Laravel’s Artisan command-line tool to generate the models and migrations for each entity.
Supplier Model and Migration:
php artisan make:model Supplier -m
Edit the migration file create_suppliers_table.php:
// database/migrations/xxxx_xx_xx_xxxxxx_create_suppliers_table.php
public function up()
{
Schema::create('suppliers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('address');
$table->string('contact_person');
$table->string('phone_number');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('suppliers');
}
This Laravel migration file (xxxx_xx_xx_xxxxxx_create_suppliers_table.php) is responsible for defining the structure of the suppliers table in your database. Let’s break down what each part of this migration does:
up() Method:
Schema Creation (
Schema::create('suppliers', function (Blueprint $table) { ... })):- This method defines a new table named
suppliers. - Blueprint Object: Inside the closure function,
$tablerepresents an instance ofBlueprint, which is used to specify the table’s columns and their attributes.
- This method defines a new table named
Columns Defined:
id(): Creates an auto-incrementing primary key columnidfor the table.string('name'): Defines a column namednameof typeVARCHAR(default length 255).string('address'): Defines a column namedaddressof typeVARCHAR.string('contact_person'): Defines a column namedcontact_personof typeVARCHAR.string('phone_number'): Defines a column namedphone_numberof typeVARCHAR.timestamps(): Addscreated_atandupdated_atcolumns to automatically record the creation and last update times of each record.
down() Method:
- Dropping the Table (
Schema::dropIfExists('suppliers')):- This method specifies how to reverse the actions performed in the
up()method. dropIfExists(): Checks if thesupplierstable exists and drops it if it does, effectively deleting the table from the database.
- This method specifies how to reverse the actions performed in the
Usage:
- To create the
supplierstable in your database, you would run the migration using Laravel’sphp artisan migratecommand. - To rollback this migration (i.e., drop the
supplierstable), you would usephp artisan migrate:rollback.
Product Model and Migration:
php artisan make:model Product -m
When you run php artisan make:model Product -m in Laravel, it performs two main actions:
1. Model Creation (Product.php):
- Model Creation: Laravel generates a
Product.phpfile under theapp\Modelsdirectory (assuming the default Laravel 8+ directory structure). - Location: This file defines a PHP class
Productthat extendsIlluminate\Database\Eloquent\Model. It serves as an ORM (Object-Relational Mapping) model representing theproductstable in your database. - Usage: The
Productmodel allows you to interact with theproductstable in an object-oriented manner, including querying, inserting, updating, and deleting records.
2. Migration Creation (create_products_table migration file):
- Migration Creation: Alongside creating the model, Laravel generates a corresponding migration file (
xxxx_xx_xx_xxxxxx_create_products_table.php) under thedatabase/migrationsdirectory. - Purpose: This migration file defines the structure of the
productstable in your database. - Structure: It includes columns such as
name,description,unit_price,quantity_available, and aforeignIdcolumn (supplier_id) that references theidcolumn in thesupplierstable. up()Method: Defines the table structure using Laravel’sSchema::create()method.down()Method: Specifies how to drop the table if needed usingSchema::dropIfExists().
Edit the migration file create_products_table.php:
// database/migrations/xxxx_xx_xx_xxxxxx_create_products_table.php
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description');
$table->decimal('unit_price', 8, 2);
$table->integer('quantity_available');
$table->foreignId('supplier_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('products');
}
Order Model and Migration:
php artisan make:model Order -m
Edit the migration file create_orders_table.php:
// database/migrations/xxxx_xx_xx_xxxxxx_create_orders_table.php
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('product_id')->constrained()->onDelete('cascade');
$table->foreignId('supplier_id')->constrained()->onDelete('cascade');
$table->date('order_date');
$table->integer('quantity_ordered');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('orders');
}
Shipment Model and Migration:
php artisan make:model Shipment -m
Edit the migration file create_shipments_table.php:
// database/migrations/xxxx_xx_xx_xxxxxx_create_shipments_table.php
public function up()
{
Schema::create('shipments', function (Blueprint $table) {
$table->id();
$table->foreignId('order_id')->constrained()->onDelete('cascade');
$table->date('shipment_date');
$table->date('estimated_arrival_date');
$table->date('actual_arrival_date')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('shipments');
}
Step 2: Defining Model Relationships
Next, we will define the relationships between the models in their respective files.
Supplier Model:
// app/Models/Supplier.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Supplier extends Model
{
use HasFactory;
protected $fillable = [
'name', 'address', 'contact_person', 'phone_number'
];
public function products()
{
return $this->hasMany(Product::class);
}
}
This Supplier model in Laravel, defined in app/Models/Supplier.php, represents the suppliers table in your database. Let’s break down its components and functionality:
Namespace and Imports:
- Namespace:
namespace App\Models; - Imports:
use Illuminate\Database\Eloquent\Factories\HasFactory;: Imports theHasFactorytrait, which provides factory support for the model.use Illuminate\Database\Eloquent\Model;: Imports the baseModelclass provided by Laravel’s Eloquent ORM.
Class Definition:
- Class:
Supplier- Extends
Model: This meansSupplierinherits all functionalities of Laravel’s EloquentModelclass. - Traits Used:
HasFactory: Allows the model to utilize factory methods for easier testing and database seeding.
- Extends
Properties and Methods:
$fillableProperty: Specifies which attributes can be mass-assigned using Eloquent’screate()orupdate()methods.- Attributes: Includes
'name','address','contact_person', and'phone_number'.
- Attributes: Includes
Relationships:
products()Method: Defines a one-to-many relationship betweenSupplierandProductmodels.- Return Type: Returns a collection of
Productmodels associated with theSupplier. - Method: Utilizes Eloquent’s
hasMany()method to establish the relationship based on foreign key conventions (supplier_idin theproductstable).
- Return Type: Returns a collection of
Example Usage:
- With this
Suppliermodel, you can perform operations such as creating, reading, updating, and deleting supplier records in thesupplierstable. - Relationship Access: You can also access related products for a supplier using methods like
$supplier->products.
Product Model:
// app/Models/Product.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $fillable = [
'name', 'description', 'unit_price', 'quantity_available', 'supplier_id'
];
public function supplier()
{
return $this->belongsTo(Supplier::class);
}
public function orders()
{
return $this->hasMany(Order::class);
}
}
Order Model:
// app/Models/Order.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
use HasFactory;
protected $fillable = [
'product_id', 'supplier_id', 'order_date', 'quantity_ordered'
];
public function product()
{
return $this->belongsTo(Product::class);
}
public function supplier()
{
return $this->belongsTo(Supplier::class);
}
public function shipment()
{
return $this->hasOne(Shipment::class);
}
}
The Order model in Laravel, located at app/Models/Order.php, represents the orders table in your database. Let’s go through its structure and relationships:
Namespace and Imports:
- Namespace:
namespace App\Models; - Imports:
use Illuminate\Database\Eloquent\Factories\HasFactory;: Imports theHasFactorytrait, providing factory support for the model.use Illuminate\Database\Eloquent\Model;: Imports the baseModelclass provided by Laravel’s Eloquent ORM.
Class Definition:
- Class:
Order- Extends
Model: This meansOrderinherits all functionalities of Laravel’s EloquentModelclass. - Traits Used:
HasFactory: Allows the model to utilize factory methods for easier testing and database seeding.
- Extends
Properties and Methods:
$fillableProperty: Specifies which attributes can be mass-assigned using Eloquent’screate()orupdate()methods.- Attributes: Includes
'product_id','supplier_id','order_date', and'quantity_ordered'.
- Attributes: Includes
Relationships:
product()Method:- Defines a
belongsTorelationship betweenOrderandProductmodels. - Return Type: Returns a single
Productmodel associated with theOrder. - Method: Utilizes Eloquent’s
belongsTo()method to establish the relationship based on foreign key conventions (product_idin theorderstable).
- Defines a
supplier()Method:- Defines a
belongsTorelationship betweenOrderandSuppliermodels. - Return Type: Returns a single
Suppliermodel associated with theOrder. - Method: Utilizes Eloquent’s
belongsTo()method to establish the relationship based on foreign key conventions (supplier_idin theorderstable).
- Defines a
shipment()Method:- Defines a
hasOnerelationship betweenOrderandShipmentmodels. - Return Type: Returns a single
Shipmentmodel associated with theOrder. - Method: Utilizes Eloquent’s
hasOne()method to specify that an order can have one associated shipment.
- Defines a
Example Usage:
- With this
Ordermodel, you can perform operations such as creating, reading, updating, and deleting order records in theorderstable. - Accessing Relationships: You can access related product, supplier, and shipment information associated with an order using methods like
$order->product,$order->supplier, and$order->shipment.
Shipment Model:
// app/Models/Shipment.php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Shipment extends Model
{
use HasFactory;
protected $fillable = [
'order_id', 'shipment_date', 'estimated_arrival_date', 'actual_arrival_date'
];
public function order()
{
return $this->belongsTo(Order::class);
}
}
Step 3: Running Migrations
Run the migrations to create the database schema:
php artisan migrate
In this module, we have created the models and migrations for the key entities in our Supply Chain Management system: Supplier, Product, Order, and Shipment. We also defined the relationships between these entities. In the next module, we will create the controllers and routes to handle CRUD operations for these models.
Module 5: Controllers and Routes Creation
In this module, we will create controllers and routes to handle CRUD (Create, Read, Update, Delete) operations for the entities in our Supply Chain Management (SCM) system. These entities include Supplier, Product, Order, and Shipment.
Step 1: Creating Controllers
We will use Laravel’s Artisan command-line tool to generate the controllers for each entity.
Supplier Controller:
php artisan make:controller SupplierController
When you run php artisan make:controller SupplierController in Laravel, it generates a new controller file named SupplierController.php in the app/Http/Controllers directory. This controller serves as a blueprint for handling HTTP requests related to suppliers in your application. Here’s an overview of what the generated controller might look like and its typical usage:
Controller File Location:
- File Path:
app/Http/Controllers/SupplierController.php
Product Controller:
php artisan make:controller ProductController
Order Controller:
php artisan make:controller OrderController
Shipment Controller:
php artisan make:controller ShipmentController
Step 2: Implementing Controller Methods
We will implement the CRUD operations in each controller.
Supplier Controller
Edit the SupplierController.php file:
// app/Http/Controllers/SupplierController.php
namespace App\Http\Controllers;
use App\Models\Supplier;
use Illuminate\Http\Request;
class SupplierController extends Controller
{
// Get all suppliers
public function index()
{
return Supplier::all();
}
// Create a new supplier
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'address' => 'required',
'contact_person' => 'required',
'phone_number' => 'required',
]);
return Supplier::create($request->all());
}
// Get a single supplier by ID
public function show($id)
{
return Supplier::find($id);
}
// Update a supplier
public function update(Request $request, $id)
{
$supplier = Supplier::find($id);
$supplier->update($request->all());
return $supplier;
}
// Delete a supplier
public function destroy($id)
{
return Supplier::destroy($id);
}
}
The SupplierController in Laravel, located at app/Http/Controllers/SupplierController.php, handles CRUD (Create, Read, Update, Delete) operations for the Supplier model. Let’s break down each method and its purpose:
Namespace and Imports:
- Namespace: Defines the
App\Http\Controllersnamespace for the controller class. - Imports:
Supplier: Imports theSuppliermodel to interact with supplier data.Request: Imports Laravel’sRequestclass for handling HTTP request data.
Controller Class and Methods:
Explanation of Methods:
index()Method:- Purpose: Retrieves all suppliers from the
supplierstable. - Implementation: Uses
Supplier::all()to fetch all records.
- Purpose: Retrieves all suppliers from the
store(Request $request)Method:- Purpose: Handles creation of a new supplier.
- Validation: Validates incoming request data using
$request->validate([...]). - Creation: Uses
Supplier::create($request->all())to create a new supplier record.
show($id)Method:- Purpose: Retrieves a single supplier by its ID.
- Implementation: Uses
Supplier::find($id)to find the supplier record with the specified ID.
update(Request $request, $id)Method:- Purpose: Updates an existing supplier.
- Fetching: Retrieves the supplier record by its ID using
Supplier::find($id). - Updating: Updates the supplier attributes with
$supplier->update($request->all()).
destroy($id)Method:- Purpose: Deletes a supplier by its ID.
- Implementation: Uses
Supplier::destroy($id)to delete the supplier record.
Notes:
- Validation: In
store()method,$request->validate([...])ensures that required fields (name,address,contact_person,phone_number) are present before creating a new supplier. - Return Values: Each method typically returns JSON responses, making it suitable for RESTful API endpoints.
Usage:
- Routing: Define routes in
routes/web.phporroutes/api.phpto map HTTP requests to these controller methods. - Middleware: Apply middleware to enforce authentication, authorization, or other checks on these endpoints.
Product Controller
Edit the ProductController.php file:
// app/Http/Controllers/ProductController.php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
// Get all products
public function index()
{
return Product::all();
}
// Create a new product
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'description' => 'required',
'unit_price' => 'required|numeric',
'quantity_available' => 'required|integer',
'supplier_id' => 'required|exists:suppliers,id',
]);
return Product::create($request->all());
}
// Get a single product by ID
public function show($id)
{
return Product::find($id);
}
// Update a product
public function update(Request $request, $id)
{
$product = Product::find($id);
$product->update($request->all());
return $product;
}
// Delete a product
public function destroy($id)
{
return Product::destroy($id);
}
}
Order Controller
Edit the OrderController.php file:
// app/Http/Controllers/OrderController.php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Http\Request;
class OrderController extends Controller
{
// Get all orders
public function index()
{
return Order::with(['product', 'supplier'])->get();
}
// Create a new order
public function store(Request $request)
{
$request->validate([
'product_id' => 'required|exists:products,id',
'supplier_id' => 'required|exists:suppliers,id',
'order_date' => 'required|date',
'quantity_ordered' => 'required|integer',
]);
return Order::create($request->all());
}
// Get a single order by ID
public function show($id)
{
return Order::with(['product', 'supplier'])->find($id);
}
// Update an order
public function update(Request $request, $id)
{
$order = Order::find($id);
$order->update($request->all());
return $order;
}
// Delete an order
public function destroy($id)
{
return Order::destroy($id);
}
}
Shipment Controller
Edit the ShipmentController.php file:
// app/Http/Controllers/ShipmentController.php
namespace App\Http\Controllers;
use App\Models\Shipment;
use Illuminate\Http\Request;
class ShipmentController extends Controller
{
// Get all shipments
public function index()
{
return Shipment::with('order')->get();
}
// Create a new shipment
public function store(Request $request)
{
$request->validate([
'order_id' => 'required|exists:orders,id',
'shipment_date' => 'required|date',
'estimated_arrival_date' => 'required|date',
'actual_arrival_date' => 'nullable|date',
]);
return Shipment::create($request->all());
}
// Get a single shipment by ID
public function show($id)
{
return Shipment::with('order')->find($id);
}
// Update a shipment
public function update(Request $request, $id)
{
$shipment = Shipment::find($id);
$shipment->update($request->all());
return $shipment;
}
// Delete a shipment
public function destroy($id)
{
return Shipment::destroy($id);
}
}
Step 3: Creating Routes
We will define the routes for each entity in the api.php file.
Update
routes/api.php:
use App\Http\Controllers\SupplierController;
use App\Http\Controllers\ProductController;
use App\Http\Controllers\OrderController;
use App\Http\Controllers\ShipmentController;
Route::apiResource('suppliers', SupplierController::class);
Route::apiResource('products', ProductController::class);
Route::apiResource('orders', OrderController::class);
Route::apiResource('shipments', ShipmentController::class);
This Laravel route definition maps HTTP endpoints to corresponding controller resources using Route::apiResource() method. Here’s a breakdown of what each line does based on your example:
Route Definitions:
Route::apiResource('suppliers', SupplierController::class);- Purpose: Defines RESTful API routes for managing suppliers.
- Mapped Controller:
SupplierController::classhandles CRUD operations for suppliers (index,store,show,update,destroy).
Route::apiResource('products', ProductController::class);- Purpose: Defines RESTful API routes for managing products.
- Mapped Controller:
ProductController::classhandles CRUD operations for products (index,store,show,update,destroy).
Route::apiResource('orders', OrderController::class);- Purpose: Defines RESTful API routes for managing orders.
- Mapped Controller:
OrderController::classhandles CRUD operations for orders (index,store,show,update,destroy).
Route::apiResource('shipments', ShipmentController::class);- Purpose: Defines RESTful API routes for managing shipments.
- Mapped Controller:
ShipmentController::classhandles CRUD operations for shipments (index,store,show,update,destroy).
Explanation:
Route::apiResource(): Laravel provides this method to generate a set of typical CRUD routes for a resourceful controller.- Resource Naming: The first parameter (
'suppliers','products','orders','shipments') specifies the URI path segment for each resource. - Controller Mapping: The second parameter (
SupplierController::class,ProductController::class,OrderController::class,ShipmentController::class) identifies the controller class that will handle requests for each resource.
Generated Routes:
- For each
Route::apiResource()call, Laravel generates multiple routes with corresponding HTTP verbs and URIs:GET /suppliers: Retrieves all suppliers.POST /suppliers: Creates a new supplier.GET /suppliers/{supplier}: Retrieves a specific supplier by ID.PUT /suppliers/{supplier}: Updates a specific supplier by ID.DELETE /suppliers/{supplier}: Deletes a specific supplier by ID.- Similarly for
products,orders, andshipments.
Usage:
- Route Naming: These routes follow RESTful conventions, making it easier to understand and maintain your API endpoints.
- Middleware: You can apply middleware globally or to specific routes/controllers to add authentication, authorization, or other request processing logic.
Step 4: Testing the Endpoints
You can use tools like Postman or Insomnia to test the CRUD operations for each entity. Below are some example requests:
Create a Supplier:
- Method: POST
- URL:
http://127.0.0.1:8000/api/suppliers - Body
{
"name": "Supplier A",
"address": "123 Supplier St",
"contact_person": "John Doe",
"phone_number": "1234567890"
}
Get All Suppliers:
- Method: GET
- URL:
http://127.0.0.1:8000/api/suppliers
Create a Product:
- Method: POST
- URL:
http://127.0.0.1:8000/api/products - Body:
{
"name": "Product A",
"description": "Description of Product A",
"unit_price": 10.00,
"quantity_available": 100,
"supplier_id": 1
}
Get All Products:
- Method: GET
- URL:
http://127.0.0.1:8000/api/products
In this module, we created controllers and routes for handling CRUD operations for suppliers, products, orders, and shipments in our Supply Chain Management system. In the next module, we will implement advanced pagination, filtering, and search functionalities.
Module 6: Advanced Pagination, Filtering, and Search
In this module, we will implement advanced pagination, filtering, and search functionalities for our Supply Chain Management (SCM) system. These features will enhance the usability of our API by allowing clients to fetch and query data more effectively.
Step 1: Implementing Pagination, Filtering, and Search
We will implement these functionalities directly within each controller. Laravel’s Eloquent ORM provides methods and capabilities to make this process straightforward.
Step 2: Enhancing the Controllers
We will modify the existing controllers to include pagination, filtering, and search functionalities.
Supplier Controller
Modify the SupplierController.php to include pagination, filtering, and search.
// app/Http/Controllers/SupplierController.php
namespace App\Http\Controllers;
use App\Models\Supplier;
use Illuminate\Http\Request;
class SupplierController extends Controller
{
// Get all suppliers with pagination, filtering, and search
public function index(Request $request)
{
$query = Supplier::query();
// Filtering
if ($request->has('name')) {
$query->where('name', 'like', '%' . $request->query('name') . '%');
}
if ($request->has('contact_person')) {
$query->where('contact_person', 'like', '%' . $request->query('contact_person') . '%');
}
// Sorting
if ($request->has('sort')) {
$sort = explode(',', $request->query('sort'));
foreach ($sort as $sortOption) {
list($sortField, $sortDirection) = explode(':', $sortOption);
$query->orderBy($sortField, $sortDirection);
}
} else {
$query->orderBy('created_at', 'desc');
}
// Pagination
$pageSize = $request->query('page_size', 10);
return $query->paginate($pageSize);
}
// Other CRUD methods...
}
The SupplierController in Laravel, located at app/Http/Controllers/SupplierController.php, demonstrates advanced functionality for handling suppliers, including pagination, filtering, and sorting. Let’s break down the index() method and its components:
Namespace and Imports:
- Namespace:
App\Http\Controllersspecifies the location of the controller class. - Imports:
Supplier: Imports theSuppliermodel to interact with supplier data.Request: Imports Laravel’sRequestclass for handling HTTP request data.
Controller Class and Methods:
Explanation of index() Method:
index(Request $request)Method:- Purpose: Retrieves a paginated list of suppliers with optional filtering and sorting based on the query parameters.
- Query Setup: Starts by initializing a query using
Supplier::query().
Filtering:
- Implementation: Checks if query parameters (
name,contact_person) are present in the request. - Usage: Uses
where()clause withlikeoperator to filter suppliers based on name or contact person.
- Implementation: Checks if query parameters (
Sorting:
- Implementation: Checks if
sortquery parameter is present. - Parsing: Splits
sortparameter by commas (explode(',', $request->query('sort'))) to handle multiple sorting criteria. - Ordering: Uses
orderBy()to dynamically sort suppliers by specified fields ($sortField) and directions ($sortDirection).
- Implementation: Checks if
Pagination:
- Configuration: Retrieves
page_sizefrom query parameters ($request->query('page_size', 10)). - Execution: Applies pagination using
paginate($pageSize)method on the$query.
- Configuration: Retrieves
Return Value:
- Response: Returns a paginated JSON response containing suppliers based on the configured pagination, filtering, and sorting.
Usage:
- Route: Ensure routes in
routes/api.phporroutes/web.phpare configured to route requests toSupplierController@index. - Request Handling: Test the API using tools like Postman, passing query parameters (
name,contact_person,sort,page_size) to observe filtered, sorted, and paginated responses.
Filtering
What is Filtering? Filtering allows you to narrow down a dataset based on specific criteria. In our case, we want to filter suppliers based on attributes like name and contact_person.
Implementation in SupplierController@index:
// Filtering
if ($request->has('name')) {
$query->where('name', 'like', '%' . $request->query('name') . '%');
}
if ($request->has('contact_person')) {
$query->where('contact_person', 'like', '%' . $request->query('contact_person') . '%');
}
Explanation:
- The
Requestobject ($request) represents the incoming HTTP request to the server. $request->query('name')retrieves the value of thenameparameter from the query string of the URL.where('name', 'like', '%' . $request->query('name') . '%')filters the suppliers where thenameattribute contains the substring provided in thenameparameter.- Similarly,
where('contact_person', 'like', '%' . $request->query('contact_person') . '%')filters based on thecontact_personattribute.
- The
Usage:
- If your API endpoint is
/api/suppliers?name=ABC&contact_person=XYZ, it filters suppliers whosenamecontains “ABC” andcontact_personcontains “XYZ”.
- If your API endpoint is
Pagination
What is Pagination? Pagination splits large datasets into smaller, manageable chunks or pages. This is particularly useful when dealing with a large number of records to improve performance and usability.
Implementation in SupplierController@index:
// Pagination
$pageSize = $request->query('page_size', 10);
return $query->paginate($pageSize);
Explanation:
$request->query('page_size', 10)retrieves thepage_sizequery parameter from the request. If not provided, it defaults to 10.paginate($pageSize)divides the query results into pages, with each page containing a maximum of$pageSizerecords.
Usage:
- When your API endpoint returns paginated data (
/api/suppliers?page=1&page_size=20), it retrieves the first 20 suppliers. Subsequent pages can be accessed by changing thepageparameter (/api/suppliers?page=2&page_size=20for the next 20 suppliers).
- When your API endpoint returns paginated data (
Combining Filtering and Pagination
Integration in SupplierController@index:
Filtering and pagination can work together seamlessly:
// Filtering if ($request->has('name')) { $query->where('name', 'like', '%' . $request->query('name') . '%'); } if ($request->has('contact_person')) { $query->where('contact_person', 'like', '%' . $request->query('contact_person') . '%'); } // Pagination $pageSize = $request->query('page_size', 10); return $query->paginate($pageSize);Explanation:
- Users can filter suppliers based on attributes like
nameandcontact_personusing query parameters (?name=ABC&contact_person=XYZ). - Pagination ensures that results are divided into manageable pages (
?page=1&page_size=10), making it easier to navigate large datasets.
- Users can filter suppliers based on attributes like
Product Controller
Modify the ProductController.php to include pagination, filtering, and search.
// app/Http/Controllers/ProductController.php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
// Get all products with pagination, filtering, and search
public function index(Request $request)
{
$query = Product::query();
// Filtering
if ($request->has('name')) {
$query->where('name', 'like', '%' . $request->query('name') . '%');
}
if ($request->has('description')) {
$query->where('description', 'like', '%' . $request->query('description') . '%');
}
// Sorting
if ($request->has('sort')) {
$sort = explode(',', $request->query('sort'));
foreach ($sort as $sortOption) {
list($sortField, $sortDirection) = explode(':', $sortOption);
$query->orderBy($sortField, $sortDirection);
}
} else {
$query->orderBy('created_at', 'desc');
}
// Pagination
$pageSize = $request->query('page_size', 10);
return $query->paginate($pageSize);
}
// Other CRUD methods...
}
The ProductController in Laravel, found in app/Http/Controllers/ProductController.php, is responsible for managing product data through various CRUD operations. Here’s a breakdown of its index() method and its functionality:
index() Method Overview:
Namespace and Imports: Defines the namespace and includes necessary imports.
Filtering: Checks if filtering parameters (
name,description) are provided in the request and applies filtering usingwhere()clauses withlikeoperators.Sorting: Parses the
sortquery parameter to determine sorting criteria (orderBy()method).Pagination: Retrieves the
page_sizequery parameter to determine how many records to display per page (paginate()method).Return: Returns paginated product data based on the applied filters, sorting, and pagination.
This setup allows for efficient handling of product data retrieval, catering to specific user queries via HTTP requests.
Order Controller
Modify the OrderController.php to include pagination, filtering, and search.
// app/Http/Controllers/OrderController.php
namespace App\Http\Controllers;
use App\Models\Order;
use Illuminate\Http\Request;
class OrderController extends Controller
{
// Get all orders with pagination, filtering, and search
public function index(Request $request)
{
$query = Order::with(['product', 'supplier']);
// Filtering
if ($request->has('order_date')) {
$query->whereDate('order_date', $request->query('order_date'));
}
if ($request->has('quantity_ordered')) {
$query->where('quantity_ordered', $request->query('quantity_ordered'));
}
// Sorting
if ($request->has('sort')) {
$sort = explode(',', $request->query('sort'));
foreach ($sort as $sortOption) {
list($sortField, $sortDirection) = explode(':', $sortOption);
$query->orderBy($sortField, $sortDirection);
}
} else {
$query->orderBy('created_at', 'desc');
}
// Pagination
$pageSize = $request->query('page_size', 10);
return $query->paginate($pageSize);
}
// Other CRUD methods...
}
Shipment Controller
Modify the ShipmentController.php to include pagination, filtering, and search.
// app/Http/Controllers/ShipmentController.php
namespace App\Http\Controllers;
use App\Models\Shipment;
use Illuminate\Http\Request;
class ShipmentController extends Controller
{
// Get all shipments with pagination, filtering, and search
public function index(Request $request)
{
$query = Shipment::with('order');
// Filtering
if ($request->has('shipment_date')) {
$query->whereDate('shipment_date', $request->query('shipment_date'));
}
if ($request->has('estimated_arrival_date')) {
$query->whereDate('estimated_arrival_date', $request->query('estimated_arrival_date'));
}
if ($request->has('actual_arrival_date')) {
$query->whereDate('actual_arrival_date', $request->query('actual_arrival_date'));
}
// Sorting
if ($request->has('sort')) {
$sort = explode(',', $request->query('sort'));
foreach ($sort as $sortOption) {
list($sortField, $sortDirection) = explode(':', $sortOption);
$query->orderBy($sortField, $sortDirection);
}
} else {
$query->orderBy('created_at', 'desc');
}
// Pagination
$pageSize = $request->query('page_size', 10);
return $query->paginate($pageSize);
}
// Other CRUD methods...
}
The ShipmentController in Laravel, located at app/Http/Controllers/ShipmentController.php, handles CRUD operations for shipments and includes functionality for filtering, sorting, and pagination. Here’s an overview of its index() method and its components:
Namespace and Imports
- Namespace: Specifies where the controller class resides (
App\Http\Controllers). - Imports: Includes necessary classes such as
Shipmentmodel andRequestclass from Laravel.
index() Method Overview
Query Setup
- Initialization: Begins by initializing a query to fetch shipments (
$query = Shipment::with('order')). Thewith('order')method eagerly loads the associatedorderrelationship to optimize querying related data.
- Initialization: Begins by initializing a query to fetch shipments (
Filtering
- Date Filtering: Checks if specific date parameters (
shipment_date,estimated_arrival_date,actual_arrival_date) are present in the request ($request->has()). If present, filters shipments based on these dates usingwhereDate()method.
- Date Filtering: Checks if specific date parameters (
Sorting
- Sorting Logic: Parses the
sortquery parameter to determine sorting criteria. If provided, iterates through each sorting option (explode(',', $request->query('sort'))) and applies sorting usingorderBy()method with specified field and direction ($sortField,$sortDirection).
- Sorting Logic: Parses the
Pagination
- Pagination Setup: Retrieves
page_sizequery parameter to determine how many shipments to display per page ($request->query('page_size', 10)). Applies pagination usingpaginate($pageSize)method to split query results into paginated segments.
- Pagination Setup: Retrieves
Return Response
- Return Statement: Returns paginated shipment data based on the applied filters, sorting, and pagination criteria.
Usage
- Endpoint: Ensure the routes (
routes/api.phporroutes/web.php) are correctly configured to route requests toShipmentController@index.- Request Handling: Test the API using tools like Postman, providing query parameters (
shipment_date,estimated_arrival_date,sort,page_size) to observe filtered, sorted, and paginated responses for shipments.
- Request Handling: Test the API using tools like Postman, providing query parameters (
Step 3: Testing the Endpoints
Use tools like Postman or Insomnia to test the advanced querying capabilities for each endpoint. Below are some example requests:
Get All Suppliers with Pagination, Filtering, and Sorting:
- Method: GET
- URL:
http://127.0.0.1:8000/api/suppliers?page=1&page_size=5&sort=name:asc,created_at:desc&name=Supplier&contact_person=John
Get All Products with Pagination, Filtering, and Sorting:
- Method: GET
- URL:
http://127.0.0.1:8000/api/products?page=1&page_size=5&sort=name:asc,created_at:desc&name=Product&description=Description
Get All Orders with Pagination, Filtering, and Sorting:
- Method: GET
- URL:
http://127.0.0.1:8000/api/orders?page=1&page_size=5&sort=order_date:asc,created_at:desc&order_date=2024-01-01&quantity_ordered=10
Get All Shipments with Pagination, Filtering, and Sorting:
- Method: GET
- URL:
http://127.0.0.1:8000/api/shipments?page=1&page_size=5&sort=shipment_date:asc,created_at:desc&shipment_date=2024-01-01&estimated_arrival_date=2024-01-05
In this module, we enhanced the controllers for our Supply Chain Management system to include advanced pagination, filtering, and search functionalities. These features will allow clients to query data more effectively and efficiently. In the next module, we will focus on testing and deploying the application.
Module 7: Testing and Deployment
In this module, we will focus on testing
Module 7: Testing and Deployment
In this module, we will focus on testing our Supply Chain Management (SCM) API to ensure its functionality and reliability. After testing, we will deploy the application online.
Step 1: Testing the Application
Testing is a crucial part of the development process. We will use PHPUnit, which is the default testing framework for Laravel, to write our tests.
Install Testing Dependencies
Laravel comes with PHPUnit pre-installed. However, if you need to install additional testing dependencies, you can do so via Composer.
composer require --dev phpunit/phpunit
Create Test Files
Create test files for each entity. Laravel makes it easy to generate test files using Artisan commands.
- Generate Test Files:
php artisan make:test SupplierTest
php artisan make:test ProductTest
php artisan make:test OrderTest
php artisan make:test ShipmentTest
Writing Tests
Open each test file and write the necessary tests for each CRUD operation.
Supplier Test
Edit the SupplierTest.php file:
// tests/Feature/SupplierTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\Supplier;
class SupplierTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function it_can_create_a_supplier()
{
$response = $this->post('/api/suppliers', [
'name' => 'Supplier A',
'address' => '123 Supplier St',
'contact_person' => 'John Doe',
'phone_number' => '1234567890',
]);
$response->assertStatus(201);
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier A',
'address' => '123 Supplier St',
]);
}
/** @test */
public function it_can_get_all_suppliers()
{
Supplier::factory()->count(3)->create();
$response = $this->get('/api/suppliers');
$response->assertStatus(200);
$this->assertCount(3, $response->json('data'));
}
/** @test */
public function it_can_get_a_single_supplier()
{
$supplier = Supplier::factory()->create();
$response = $this->get("/api/suppliers/{$supplier->id}");
$response->assertStatus(200);
$response->assertJson(['name' => $supplier->name]);
}
/** @test */
public function it_can_update_a_supplier()
{
$supplier = Supplier::factory()->create();
$response = $this->put("/api/suppliers/{$supplier->id}", [
'name' => 'Supplier B',
'address' => '456 Supplier St',
'contact_person' => 'Jane Doe',
'phone_number' => '0987654321',
]);
$response->assertStatus(200);
$this->assertDatabaseHas('suppliers', [
'name' => 'Supplier B',
'address' => '456 Supplier St',
]);
}
/** @test */
public function it_can_delete_a_supplier()
{
$supplier = Supplier::factory()->create();
$response = $this->delete("/api/suppliers/{$supplier->id}");
$response->assertStatus(200);
$this->assertDatabaseMissing('suppliers', ['id' => $supplier->id]);
}
}
The SupplierTest class in Laravel, located at tests/Feature/SupplierTest.php, is a set of feature tests designed to validate the functionality of the SupplierController through various HTTP requests. Let’s break down each test method and its purpose:
Namespace and Imports
- Namespace:
Tests\Featurespecifies the location of the test class. - Imports: Includes necessary classes such as
RefreshDatabasefor refreshing the database state andTestCasefor defining test cases.
Feature Tests in SupplierTest
Creating a Supplier (
it_can_create_a_supplier):- Purpose: Validates that the API can successfully create a new supplier by sending a
POSTrequest to/api/suppliers. - Steps:
- Sends a
POSTrequest with supplier data (name,address,contact_person,phone_number). - Asserts that the response status is
201 Created. - Asserts that the newly created supplier exists in the database (
assertDatabaseHas).
- Sends a
- Purpose: Validates that the API can successfully create a new supplier by sending a
Getting All Suppliers (
it_can_get_all_suppliers):- Purpose: Checks that the API can retrieve all suppliers by sending a
GETrequest to/api/suppliers. - Steps:
- Uses Laravel’s model factory to create three suppliers.
- Sends a
GETrequest to retrieve all suppliers. - Asserts that the response status is
200 OK. - Asserts that the response JSON data contains three suppliers (
assertCount).
- Purpose: Checks that the API can retrieve all suppliers by sending a
Getting a Single Supplier (
it_can_get_a_single_supplier):- Purpose: Verifies that the API can fetch a specific supplier by sending a
GETrequest to/api/suppliers/{id}. - Steps:
- Creates a single supplier using Laravel’s model factory.
- Sends a
GETrequest to fetch the supplier by its ID. - Asserts that the response status is
200 OK. - Asserts that the returned JSON data matches the expected supplier’s name (
assertJson).
- Purpose: Verifies that the API can fetch a specific supplier by sending a
Updating a Supplier (
it_can_update_a_supplier):- Purpose: Tests that the API can update an existing supplier by sending a
PUTrequest to/api/suppliers/{id}. - Steps:
- Creates a supplier using Laravel’s model factory.
- Sends a
PUTrequest with updated supplier data (name,address,contact_person,phone_number). - Asserts that the response status is
200 OK. - Asserts that the database reflects the updated supplier details (
assertDatabaseHas).
- Purpose: Tests that the API can update an existing supplier by sending a
Deleting a Supplier (
it_can_delete_a_supplier):- Purpose: Ensures that the API can delete a supplier by sending a
DELETErequest to/api/suppliers/{id}. - Steps:
- Creates a supplier using Laravel’s model factory.
- Sends a
DELETErequest to remove the supplier. - Asserts that the response status is
200 OK. - Asserts that the supplier is no longer present in the database (
assertDatabaseMissing).
- Purpose: Ensures that the API can delete a supplier by sending a
Product Test
Edit the ProductTest.php file:
// tests/Feature/ProductTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\Product;
use App\Models\Supplier;
class ProductTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function it_can_create_a_product()
{
$supplier = Supplier::factory()->create();
$response = $this->post('/api/products', [
'name' => 'Product A',
'description' => 'Description of Product A',
'unit_price' => 10.00,
'quantity_available' => 100,
'supplier_id' => $supplier->id,
]);
$response->assertStatus(201);
$this->assertDatabaseHas('products', [
'name' => 'Product A',
'description' => 'Description of Product A',
]);
}
/** @test */
public function it_can_get_all_products()
{
Product::factory()->count(3)->create();
$response = $this->get('/api/products');
$response->assertStatus(200);
$this->assertCount(3, $response->json('data'));
}
/** @test */
public function it_can_get_a_single_product()
{
$product = Product::factory()->create();
$response = $this->get("/api/products/{$product->id}");
$response->assertStatus(200);
$response->assertJson(['name' => $product->name]);
}
/** @test */
public function it_can_update_a_product()
{
$product = Product::factory()->create();
$response = $this->put("/api/products/{$product->id}", [
'name' => 'Product B',
'description' => 'Description of Product B',
'unit_price' => 15.00,
Order Test
Edit the OrderTest.php file:
// tests/Feature/OrderTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\Order;
use App\Models\Product;
use App\Models\Supplier;
class OrderTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function it_can_create_an_order()
{
$supplier = Supplier::factory()->create();
$product = Product::factory()->create(['supplier_id' => $supplier->id]);
$response = $this->post('/api/orders', [
'product_id' => $product->id,
'supplier_id' => $supplier->id,
'order_date' => now()->toDateString(),
'quantity_ordered' => 10,
]);
$response->assertStatus(201);
$this->assertDatabaseHas('orders', [
'product_id' => $product->id,
'supplier_id' => $supplier->id,
]);
}
/** @test */
public function it_can_get_all_orders()
{
Order::factory()->count(3)->create();
$response = $this->get('/api/orders');
$response->assertStatus(200);
$this->assertCount(3, $response->json('data'));
}
/** @test */
public function it_can_get_a_single_order()
{
$order = Order::factory()->create();
$response = $this->get("/api/orders/{$order->id}");
$response->assertStatus(200);
$response->assertJson(['id' => $order->id]);
}
/** @test */
public function it_can_update_an_order()
{
$order = Order::factory()->create();
$response = $this->put("/api/orders/{$order->id}", [
'quantity_ordered' => 20,
]);
$response->assertStatus(200);
$this->assertDatabaseHas('orders', [
'id' => $order->id,
'quantity_ordered' => 20,
]);
}
/** @test */
public function it_can_delete_an_order()
{
$order = Order::factory()->create();
$response = $this->delete("/api/orders/{$order->id}");
$response->assertStatus(200);
$this->assertDatabaseMissing('orders', ['id' => $order->id]);
}
}
Shipment Test
Edit the ShipmentTest.php file:
// tests/Feature/ShipmentTest.php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\Shipment;
use App\Models\Order;
use App\Models\Product;
use App\Models\Supplier;
class ShipmentTest extends TestCase
{
use RefreshDatabase;
/** @test */
public function it_can_create_a_shipment()
{
$supplier = Supplier::factory()->create();
$product = Product::factory()->create(['supplier_id' => $supplier->id]);
$order = Order::factory()->create(['product_id' => $product->id, 'supplier_id' => $supplier->id]);
$response = $this->post('/api/shipments', [
'order_id' => $order->id,
'shipment_date' => now()->toDateString(),
'estimated_arrival_date' => now()->addDays(5)->toDateString(),
'actual_arrival_date' => null,
]);
$response->assertStatus(201);
$this->assertDatabaseHas('shipments', [
'order_id' => $order->id,
'shipment_date' => now()->toDateString(),
]);
}
/** @test */
public function it_can_get_all_shipments()
{
Shipment::factory()->count(3)->create();
$response = $this->get('/api/shipments');
$response->assertStatus(200);
$this->assertCount(3, $response->json('data'));
}
/** @test */
public function it_can_get_a_single_shipment()
{
$shipment = Shipment::factory()->create();
$response = $this->get("/api/shipments/{$shipment->id}");
$response->assertStatus(200);
$response->assertJson(['id' => $shipment->id]);
}
/** @test */
public function it_can_update_a_shipment()
{
$shipment = Shipment::factory()->create();
$response = $this->put("/api/shipments/{$shipment->id}", [
'actual_arrival_date' => now()->toDateString(),
]);
$response->assertStatus(200);
$this->assertDatabaseHas('shipments', [
'id' => $shipment->id,
'actual_arrival_date' => now()->toDateString(),
]);
}
/** @test */
public function it_can_delete_a_shipment()
{
$shipment = Shipment::factory()->create();
$response = $this->delete("/api/shipments/{$shipment->id}");
$response->assertStatus(200);
$this->assertDatabaseMissing('shipments', ['id' => $shipment->id]);
}
}
Step 2: Running the Tests
Run the tests using the following command:
php artisan test
This command will execute all the test files and provide a report on their success or failure.
Step 3: Deploying the Application
We will deploy the application to a cloud platform. For this example, we will use Heroku.
Step-by-Step Deployment on Heroku
Install the Heroku CLI:
Follow the instructions on the Heroku Dev Center to install the Heroku CLI.
Login to Heroku:
heroku login
Create a Heroku App:
heroku create scm-api-app
Add a Heroku Buildpack for PHP:
heroku buildpacks:set heroku/php
Set Environment Variables:
Set the environment variables for your database and any other required variables using the Heroku CLI or the Heroku dashboard.
heroku config:set DB_CONNECTION=mysql DB_HOST=your_db_host DB_PORT=3306 DB_DATABASE=your_db_name DB_USERNAME=your_db_username DB_PASSWORD=your_db_password
heroku config:set APP_KEY=$(php artisan key:generate --show)
Deploy the Application:
Initialize a Git repository if you haven’t already, add the Heroku remote, and push the code to Heroku.
git init
git add .
git commit -m "Initial commit"
git remote add heroku https://git.heroku.com/scm-api-app.git
git push heroku master
Run Migrations:
heroku run php artisan migrate
Open the App:
heroku open
Your SCM API should now be live on Heroku. You can use the Heroku dashboard to manage your app, view logs, and scale your application as needed.
Welcome to DevTechTutor.com, your ultimate resource for mastering web development and technology! Whether you're a beginner eager to dive into coding or an experienced developer looking to sharpen your skills, DevTechTutor.com is here to guide you every step of the way. Our mission is to make learning web development accessible, engaging, and effective.