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
.env
file 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 referencingsuppliers
timestamps
: Created at and updated at timestamps
Orders
id
: Primary Keyproduct_id
: Foreign Key referencingproducts
supplier_id
: Foreign Key referencingsuppliers
order_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 referencingorders
shipment_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/migrations
directory 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
Schema
class to create a new table namedusers
. - Inside the
Schema::create
method, aBlueprint
object ($table
) is used to define the structure of theusers
table. id()
: Adds an auto-incrementing primary key column namedid
.string('username')->unique()
: Adds ausername
column of type string (varchar) that must be unique across all records in the table.string('email')->unique()
: Adds anemail
column of type string (varchar) that must be unique across all records in the table.string('password')
: Adds apassword
column of type string (varchar) to store hashed passwords securely.enum('role', ['admin', 'employee', 'customer'])->default('customer')
: Adds arole
column 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_at
andupdated_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 theusers
table 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/migrations
directory 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
Schema
class to create a new table namedsuppliers
. - Inside the
Schema::create
method, aBlueprint
object ($table
) is used to define the structure of thesuppliers
table. id()
: Adds an auto-incrementing primary key column namedid
.string('name')
: Adds aname
column of type string (varchar) to store the name of the supplier.string('address')
: Adds anaddress
column of type string (varchar) to store the supplier’s address.string('contact_person')
: Adds acontact_person
column of type string (varchar) to store the name of the contact person at the supplier.string('phone_number')
: Adds aphone_number
column of type string (varchar) to store the supplier’s phone number.timestamps()
: Adds two columns,created_at
andupdated_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 thesuppliers
table 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/migrations
directory 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
Schema
class to create a new table namedproducts
. - Inside the
Schema::create
method, aBlueprint
object ($table
) is used to define the structure of theproducts
table. id()
: Adds an auto-incrementing primary key column namedid
.string('name')
: Adds aname
column of type string (varchar) to store the name of the product.text('description')
: Adds adescription
column of type text to store a detailed description of the product.decimal('unit_price', 8, 2)
: Adds aunit_price
column of type decimal to store the price of the product. The parameters8, 2
specify that it can hold up to 8 digits in total with 2 digits after the decimal point.integer('quantity_available')
: Adds aquantity_available
column of type integer to store the number of units of the product available in stock.foreignId('supplier_id')->constrained()->onDelete('cascade')
: Adds asupplier_id
column as a foreign key that references theid
column in thesuppliers
table. Theconstrained()
method ensures referential integrity (thesupplier_id
must exist in thesuppliers
table). TheonDelete('cascade')
option specifies that if a supplier is deleted, all associated products should also be deleted.timestamps()
: Adds two columns,created_at
andupdated_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 theproducts
table 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/migrations
directory. The file name will follow a format likexxxx_xx_xx_xxxxxx_create_orders_table.php
, wherexxxx_xx_xx_xxxxxx
is a timestamp that ensures migrations are run in the order they were created.
Purpose of the Migration File:
- Creating the
orders
Table: The generated migration file will contain methods (up()
anddown()
) where you can define the schema for theorders
table.
Typical Structure of the Migration File:
In the
up()
method, you’ll define how theorders
table 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) theorders
table 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/migrations
directory 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
Schema
class to create a new table namedorders
. - Inside the
Schema::create
method, aBlueprint
object ($table
) is used to define the structure of theorders
table. id()
: Adds an auto-incrementing primary key column namedid
.foreignId('product_id')->constrained()->onDelete('cascade')
: Adds aproduct_id
column as a foreign key that references theid
column in theproducts
table. Theconstrained()
method ensures referential integrity (theproduct_id
must exist in theproducts
table). 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_id
column as a foreign key that references theid
column in thesuppliers
table. 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_date
column of type date to store the date when the order was placed.integer('quantity_ordered')
: Adds aquantity_ordered
column of type integer to store the number of units of the product ordered in that specific order.timestamps()
: Adds two columns,created_at
andupdated_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 theorders
table 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
SanctumServiceProvider
and 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
, orresources
directories 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 inSanctumServiceProvider
is 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:api
suffix indicates that the throttling settings specified in yourapp/Http/Kernel.php
file under thethrottle
middleware 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 correspondingUser
model 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
EnsureFrontendRequestsAreStateful
middleware ensures that requests from your frontend application are properly authenticated and maintain session state. - The
throttle:api
middleware limits the rate of API requests to prevent abuse or excessive load on your server. SubstituteBindings
middleware 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.php
file under the$middlewareGroups
property, 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
sanctum
driver. 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 theusers
table 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 theusers
table of the database.
Usage:
You would typically define this guard configuration in your
config/auth.php
file 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 theUser
model class resides. Namespaces help organize and group classes logically within a Laravel application.
Imports:
use Illuminate\Contracts\Auth\MustVerifyEmail;
: Imports theMustVerifyEmail
contract. This indicates that theUser
model can implement email verification features if desired.use Illuminate\Foundation\Auth\User as Authenticatable;
: Imports Laravel’sAuthenticatable
class, which provides foundational authentication functionality for theUser
model.use Illuminate\Notifications\Notifiable;
: Imports theNotifiable
trait, which adds support for sending notifications to users.use Laravel\Sanctum\HasApiTokens;
: Imports theHasApiTokens
trait from Laravel Sanctum. This trait allows theUser
model to issue API tokens and interact with Sanctum’s token-based authentication features.
Class Definition:
class User extends Authenticatable
: Defines theUser
class, extending Laravel’sAuthenticatable
class. This means theUser
model inherits methods and properties for authentication and authorization fromAuthenticatable
.
Traits:
use HasApiTokens, Notifiable;
: Uses theHasApiTokens
andNotifiable
traits in theUser
model. 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 theUser
model, integrating with Laravel Sanctum.Notifiable
: Enables theUser
model to send notifications using Laravel’s notification system.
Properties:
$fillable
: Specifies which attributes of theUser
model 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 adatetime
type, ensuring it is treated as aDateTime
object.
Purpose:
Authentication and Authorization: The
User
model is central to handling user authentication and authorization within a Laravel application. By extendingAuthenticatable
and usingHasApiTokens
, it integrates with Laravel’s authentication system and provides API token management via Sanctum.Notifications: With the
Notifiable
trait, theUser
model gains the ability to send notifications, such as email notifications for password resets or account activations.Data Management: The
$fillable
,$hidden
, and$casts
properties 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
Validator
class. It checks for required fields (username
,email
,password
) and ensures uniqueness forusername
andemail
. - 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
User
record usingUser::create()
. The password is hashed usingHash::make()
for security. Ifrole
is 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_token
andtoken_type
(Bearer
).
login(Request $request)
:- This method handles user login.
- Validation: It validates the incoming request data for
email
andpassword
. - 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_token
andtoken_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
register
method within theAuthController
class. 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
login
method within theAuthController
class. 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
logout
method within theAuthController
class. 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/logout
endpoint 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 theAuthController
class.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:sanctum
middleware applied to the/logout
route 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
RoleMiddleware
is the identifier for the middleware class being created. By convention, middleware classes in Laravel are named with theMiddleware
suffix 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/Middleware
directory of your Laravel application.
Purpose of RoleMiddleware
:
- Authorization: You can implement logic inside
RoleMiddleware
to 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$routeMiddleware
array. - Inside routes or controllers, you can then apply
RoleMiddleware
either 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 theClosure
class, which represents the next middleware closure or the controller action.use Illuminate\Http\Request;
: Imports theRequest
class for handling HTTP request objects.use Illuminate\Support\Facades\Auth;
: Imports theAuth
facade for accessing authentication services.
Class Definition:
- Class:
RoleMiddleware
- This class is defined in the
App\Http\Middleware
namespace 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 (Auth
facade). - Role Check: Verifies if the authenticated user’s role matches the specified
$role
parameter (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
403
HTTP 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.php
file under the$routeMiddleware
array. - Applying Middleware: You can apply
RoleMiddleware
to 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:
$routeMiddleware
Property:- 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
role
Middleware:- To add your
RoleMiddleware
class to the$routeMiddleware
array, 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 yourRoleMiddleware
class, which checks if the authenticated user has theadmin
role before allowing access to the/admin/dashboard
route.
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:sanctum
middleware to ensure the user is authenticated using Laravel Sanctum. Then, they will pass through therole:admin
middleware, which checks if the authenticated user has theadmin
role.
- 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 theadmin
role. This is implemented in yourRoleMiddleware
class, where it verifies the user’s role against the required role (admin
in this case). If the user does not have theadmin
role, the middleware will return a403
Forbidden 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-route
to only those users who are authenticated via Sanctum and have theadmin
role.
Example Result:
- When an authenticated user with the
admin
role accesses/admin-only-route
, they will receive the JSON response'Welcome, Admin!'
. - If an authenticated user without the
admin
role tries to access the route, therole:admin
middleware will block access and return a403
Forbidden 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,
$table
represents 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 columnid
for the table.string('name')
: Defines a column namedname
of typeVARCHAR
(default length 255).string('address')
: Defines a column namedaddress
of typeVARCHAR
.string('contact_person')
: Defines a column namedcontact_person
of typeVARCHAR
.string('phone_number')
: Defines a column namedphone_number
of typeVARCHAR
.timestamps()
: Addscreated_at
andupdated_at
columns 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 thesuppliers
table 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
suppliers
table in your database, you would run the migration using Laravel’sphp artisan migrate
command. - To rollback this migration (i.e., drop the
suppliers
table), 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.php
file under theapp\Models
directory (assuming the default Laravel 8+ directory structure). - Location: This file defines a PHP class
Product
that extendsIlluminate\Database\Eloquent\Model
. It serves as an ORM (Object-Relational Mapping) model representing theproducts
table in your database. - Usage: The
Product
model allows you to interact with theproducts
table 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/migrations
directory. - Purpose: This migration file defines the structure of the
products
table in your database. - Structure: It includes columns such as
name
,description
,unit_price
,quantity_available
, and aforeignId
column (supplier_id
) that references theid
column in thesuppliers
table. 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 theHasFactory
trait, which provides factory support for the model.use Illuminate\Database\Eloquent\Model;
: Imports the baseModel
class provided by Laravel’s Eloquent ORM.
Class Definition:
- Class:
Supplier
- Extends
Model
: This meansSupplier
inherits all functionalities of Laravel’s EloquentModel
class. - Traits Used:
HasFactory
: Allows the model to utilize factory methods for easier testing and database seeding.
- Extends
Properties and Methods:
$fillable
Property: 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 betweenSupplier
andProduct
models.- Return Type: Returns a collection of
Product
models associated with theSupplier
. - Method: Utilizes Eloquent’s
hasMany()
method to establish the relationship based on foreign key conventions (supplier_id
in theproducts
table).
- Return Type: Returns a collection of
Example Usage:
- With this
Supplier
model, you can perform operations such as creating, reading, updating, and deleting supplier records in thesuppliers
table. - 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 theHasFactory
trait, providing factory support for the model.use Illuminate\Database\Eloquent\Model;
: Imports the baseModel
class provided by Laravel’s Eloquent ORM.
Class Definition:
- Class:
Order
- Extends
Model
: This meansOrder
inherits all functionalities of Laravel’s EloquentModel
class. - Traits Used:
HasFactory
: Allows the model to utilize factory methods for easier testing and database seeding.
- Extends
Properties and Methods:
$fillable
Property: 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
belongsTo
relationship betweenOrder
andProduct
models. - Return Type: Returns a single
Product
model associated with theOrder
. - Method: Utilizes Eloquent’s
belongsTo()
method to establish the relationship based on foreign key conventions (product_id
in theorders
table).
- Defines a
supplier()
Method:- Defines a
belongsTo
relationship betweenOrder
andSupplier
models. - Return Type: Returns a single
Supplier
model associated with theOrder
. - Method: Utilizes Eloquent’s
belongsTo()
method to establish the relationship based on foreign key conventions (supplier_id
in theorders
table).
- Defines a
shipment()
Method:- Defines a
hasOne
relationship betweenOrder
andShipment
models. - Return Type: Returns a single
Shipment
model 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
Order
model, you can perform operations such as creating, reading, updating, and deleting order records in theorders
table. - 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\Controllers
namespace for the controller class. - Imports:
Supplier
: Imports theSupplier
model to interact with supplier data.Request
: Imports Laravel’sRequest
class for handling HTTP request data.
Controller Class and Methods:
Explanation of Methods:
index()
Method:- Purpose: Retrieves all suppliers from the
suppliers
table. - 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.php
orroutes/api.php
to 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::class
handles 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::class
handles 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::class
handles 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::class
handles 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\Controllers
specifies the location of the controller class. - Imports:
Supplier
: Imports theSupplier
model to interact with supplier data.Request
: Imports Laravel’sRequest
class 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 withlike
operator to filter suppliers based on name or contact person.
- Implementation: Checks if query parameters (
Sorting:
- Implementation: Checks if
sort
query parameter is present. - Parsing: Splits
sort
parameter 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_size
from 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.php
orroutes/web.php
are 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
Request
object ($request
) represents the incoming HTTP request to the server. $request->query('name')
retrieves the value of thename
parameter from the query string of the URL.where('name', 'like', '%' . $request->query('name') . '%')
filters the suppliers where thename
attribute contains the substring provided in thename
parameter.- Similarly,
where('contact_person', 'like', '%' . $request->query('contact_person') . '%')
filters based on thecontact_person
attribute.
- The
Usage:
- If your API endpoint is
/api/suppliers?name=ABC&contact_person=XYZ
, it filters suppliers whosename
contains “ABC” andcontact_person
contains “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_size
query 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$pageSize
records.
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 thepage
parameter (/api/suppliers?page=2&page_size=20
for 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
name
andcontact_person
using 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 withlike
operators.Sorting: Parses the
sort
query parameter to determine sorting criteria (orderBy()
method).Pagination: Retrieves the
page_size
query 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
Shipment
model andRequest
class 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 associatedorder
relationship 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
sort
query 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_size
query 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.php
orroutes/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\Feature
specifies the location of the test class. - Imports: Includes necessary classes such as
RefreshDatabase
for refreshing the database state andTestCase
for 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
POST
request to/api/suppliers
. - Steps:
- Sends a
POST
request 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
GET
request to/api/suppliers
. - Steps:
- Uses Laravel’s model factory to create three suppliers.
- Sends a
GET
request 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
GET
request to/api/suppliers/{id}
. - Steps:
- Creates a single supplier using Laravel’s model factory.
- Sends a
GET
request 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
PUT
request to/api/suppliers/{id}
. - Steps:
- Creates a supplier using Laravel’s model factory.
- Sends a
PUT
request 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
DELETE
request to/api/suppliers/{id}
. - Steps:
- Creates a supplier using Laravel’s model factory.
- Sends a
DELETE
request 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.