Eloquent Relationship: One To One
Creating a one-to-one relationship between an author and a profile in Laravel using Eloquent involves several steps, including creating migrations, models, and defining the relationship.
Create Migrations and model
php artisan make:model Author --migration php artisan make:model Profil --migration
Edit the migrations to define the structure of your tables.
authors migration may look something like this:
Schema::create('authors', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); });
profiles migration should include a foreign key referencing the authors table:
Schema::create('profiles', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('author_id'); $table->string('bio'); $table->timestamps(); $table->foreign('author_id')->references('id')->on('authors')->onDelete('cascade'); });
Let's break down what each part of this statement does:
In your Eloquent models, define the one-to-one relationship.
In the Author model:
public function profile() { return $this->hasOne(Profil::class); }
In the Profil model:
public function author() { return $this->belongsTo(Author::class); }
Using the Relationship
With the relationships defined, you can now easily access the profile of an author and
do the opposite ;
To access the profile of an author:
$author = Author::find(1); $profile = $author->profile;
To access the author of a profile:
$profile = Profile::find(1); $author = $profile->author;
using tinker to associate an author with a profile
$author = new Author(); $author->save(); $profile = new Profile(); $author->profile()->save($profile); // $authors = Author::get(); // return list of authors with their profile $authors = Author::with('profile')->get(); // return only one author $author = Author::with('profile')->whereId(1)->first(); // list of profiles : $profiles = Profile::get(); // list of profiles with authors : $profiles = Profile::with('author')->get();
Eloquent Relationship: One To Many
Creating a one-to-Many relationship between an post and a comment in Laravel using Eloquent involves several steps, including creating migrations, models, and defining the relationship.
Create Migrations and Models
php artisan make:model Comment --migration
The comments migration should include a foreign key referencing the posts table:
Schema::create('comments', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('post_id'); $table->text('comment'); $table->timestamps(); $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade'); });
Now, define the relationships in the Eloquent models.
In the Post model, define a relationship to access its comments:
public function comments() { return $this->hasMany(Comment::class); }
In the Comment model, define a relationship to access its parent post:
public function post() { return $this->belongsTo(Post::class); }
Using Tinker or in your application logic, you can associate a comment with a post as follows:
$comment = new Comment(); $comment->comment= "first comment"; $post = new Post(); $post->comments()->save($comment);
// method 1 $comment = new Comment(); $comment->comment= "first comment"; $comment->post_id = x ; $comment->save(); // method 2 : associate two comment to one post $comment = new Comment(); $comment->comment= "first comment"; $comment1 = new Comment(); $comment1->comment= "second comment "; $post = Post::find(3); $post->comments()->saveMany([$comment,$comment1]);
Eloquent Relationship: Many To Many
create a many-to-many relationship in Laravel, let's use an example involving posts, comments, and introduce a new entity, tags, to demonstrate a many-to-many relationship, since a comment typically belongs to one post directly, making it a one-to-many relationship. Tags can be applied to posts and posts can have multiple tags, perfectly illustrating a many-to-many relationship.
First, create a model for Tag with a migration, and also create a pivot table migration for the many-to-many relationship between posts and tags.
bashCopy code php artisan make:model Tag --migration php artisan make:migration create_post_tag_table --create=post_tag
Edit the migration for tags to add the necessary fields.
phpCopy code Schema::create('tags', function (Blueprint $table) { $table->id(); $table->string('name'); $table->timestamps(); });
Define the pivot table post_tag to store the relationships between posts and tags.
phpCopy code Schema::create('post_tag', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('post_id'); $table->unsignedBigInteger('tag_id'); $table->timestamps(); $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); });
In the Post model, define a relationship to access its tags.
public function tags() { return $this->belongsToMany(Tag::class); }
In the Tag model, define a relationship to access its posts.
public function posts() { return $this->belongsToMany(Post::class); }
You can associate tags with a post using Tinker or within your application logic. Here's an example of how to attach a tag to a post:
phpCopy code $post = Post::find(1); $tag = Tag::find(1); // Attach the tag to the post $post->tags()->attach($tag->id); // Alternatively, if you have multiple tags to attach at once $post->tags()->attach([$tagId1, $tagId2]);
To detach tags from a post:
// Detach a single tag from the post $post->tags()->detach($tagId); // Detach multiple tags from the post $post->tags()->detach([$tagId1, $tagId2]); // Detach all tags from the post $post->tags()->detach();
This setup allows you to have a many-to-many relationship between posts and tags, where a post can have multiple tags and a tag can be associated with multiple posts.
In the provided code snippets, we demonstrate two different approaches to retrieving a Comment model in Laravel, showcasing the difference between lazy and eager loading.
Lazy Loading Example:
$comment = comment::find(2);
In this line, we are retrieving the comment with ID 2 using the find method. This is an example of lazy loading, where the associated Post model is not loaded at the same time as the Comment. The Post data will only be loaded when explicitly accessed, for example, $comment->post, which would trigger an additional query to the database at that point.
Eager Loading Example:
$comment = comment::with('post')->find(2);
Here, we use the with('post') method to eagerly load the related Post model at the same time as the Comment. This means when the comment with ID 2 is fetched, the related Post is also retrieved in the same query operation. Eager loading is beneficial for performance when you know you will need the related model immediately, as it reduces the total number of queries made to the database.
In summary, the choice between lazy and eager loading in Laravel depends on the specific requirements of your application and when you need access to related models. Eager loading is generally used to optimize performance by reducing the number of database queries.
Example with our Project :
// mode lazy public function index() { DB::connection()->enableQueryLog(); $posts = Post::all(); foreach ($posts as $post) { foreach ($post->comments as $comment) { dump($comment); } } return view('posts.index', [ 'posts' => $posts]); } // mode eager public function index() { DB::connection()->enableQueryLog(); $posts = Post::with('comments')->get(); foreach ($posts as $post) { foreach ($post->comments as $comment) { dump($comment); } } return view('posts.index', [ 'posts' => $posts]); }
Model Factories
Model Factories in Laravel are a powerful feature for testing and seeding your database with dummy data. They allow you to easily generate fake data for your Eloquent models, which is extremely useful for automated testing and during development when you need to work with sample data. Here's a detailed description suitable for a course module:
Example
php artisan make:factory PostFactory
*/ class PostFactory extends Factory { /** * Define the model's default state. * * @return array*/ public function definition(): array { $title = fake()->sentence(); return [ 'title' => fake()->sentence(), 'content' => fake()->text(2000), 'slug' => \Illuminate\Support\Str::slug($title), 'active' => fake()->boolean(), ]; } }
Execute factory in the Tinker :
php artisan tinker Post::factory()->create();
php artisan make:migration add_userid_to_posts_table
The $table->foreignId('user_id')->constrained(); line in a Laravel migration represents the definition of a foreign key constraint within your database schema. Here's a breakdown of what each part means:
This line effectively does two things:
Database Seeding
Definition: Database seeding is the method of automatically populating a database with data. This data can be static, meant for configuration or initial state setup, or dynamically generated to simulate a database filled with user-generated content. Seeding is especially useful for developers and testers by providing a quick way to insert test data into a database without manually entering it.
Purpose:
How It Works:
Best Practices:
\App\Models\User::factory(10)->create(); //\App\Models\User::factory()->create([ // 'name' => 'Test User', // 'email' => '[email protected]', // ]); \App\Models\Post::factory(10)->create();
Model relations inside seeder
When working with database seeders in a Laravel application, it's often necessary to create not just standalone records, but also to establish relationships between them. This is crucial for reflecting the real-world connections between different entities in your application, such as users having posts, or orders being linked to customers. Here's how you can handle model relationships inside a database seeder in Laravel.
When seeding data, you typically start by creating instances of your root model. For related models, you can utilize Laravel's relationship methods to create and associate them seamlessly.
Suppose you have two models, User and Post, with a one-to-many relationship (a user can have many posts). Here's how you could seed this relationship:
//Model relations inside seeder $users = \App\Models\User::factory(10)->create(); \App\Models\Post::factory(10)->make()->each(function ($post) use ($users) { $post->user_id = $users->random()->id; $post->save(); });
Individual seeder classes
In Laravel, using individual seeder classes for each model can help organize your database seeds, making them more maintainable and readable, especially in larger applications. This approach allows you to separate the seeding logic for different parts of your application into distinct classes. Here's a guide on how to use individual seeder classes effectively.
To create a new seeder class, you can use the Artisan command provided by Laravel. For example, to create a seeder for the User model, you would run:
php artisan make:seeder UsersTableSeeder php artisan make:seeder PostsTableSeeder
User seeder
create(); } }
Post Seeder
make()->each(function ($post) use ($users) { $post->user_id = $users->random()->id; $post->save(); }); } }
Linking Seeder Classes in DatabaseSeeder
The DatabaseSeeder class is the root seeder class that's called when you run the database seed command. To use your individual seeder classes, you need to call them from the run method of the DatabaseSeeder class:
$this->call([ UsersTableSeeder::class, PostsTableSeeder::class, ]);
Making seeder interactive
Making a Laravel seeder interactive allows you to dynamically specify aspects of the seeding process at runtime, such as how many records to create. This approach can be particularly useful during development or when running seeders in different environments, providing flexibility without the need to modify the seeder code for each run. Here's how you can make a Laravel seeder interactive:
Laravel seeders can interact with the user through the CLI by using the ask method available in the seeder class. This method is part of the Illuminate\Console\Command class, which seeders have access to via the command property.
Let's create an interactive seeder that asks how many users to create.
command->ask('How many users do you want to create?', 10); // Create the specified number of users using the User factory \App\Models\User::factory((int)$count)->create(); $this->command->info("Successfully created {$count} users."); // \App\Models\User::factory(10)->create(); } }
refresh database before creating new seeders
if($this->command->confirm('Do you want to refresh the database?', true)) { $this->command->call('migrate:refresh'); $this->command->info('Database was refreshed.'); } $this->call([ UsersTableSeeder::class, PostsTableSeeder::class, ]);
check if users exist
count() == 0) { $this->command->info('Please create some users first.'); return; } \App\Models\Post::factory(10)->make()->each(function ($post) use ($users) { $post->user_id = $users->random()->id; $post->save(); }); } }
php artisan db:seed --class=PostsTableSeeder
Unit Testing
Creating a comprehensive tutorial on testing user and post functionality in a Laravel application involves several steps, including setting up the environment, writing tests for both models and their relationships, and ensuring that all functionalities work as expected. This tutorial will cover the basics of setting up Laravel for testing, creating unit tests for models, and feature tests for user interactions with posts.
First, ensure your Laravel application is set up for testing. Laravel uses PHPUnit for testing, configured out of the box. Check that the phpunit.xml file exists in your project root; this file configures your testing environment. You might want to configure your database for testing by setting the DB_CONNECTION environment variable in phpunit.xml to use a different database, like SQLite, for faster tests.
php artisan test
php artisan make:test HomeTest
get('/home'); $response->assertStatus(200); } public function test_home_page_contains_text(): void { $response = $this->get('/home'); $response->assertSeeText('Home pge'); } }
Before writing feature tests, it's a good practice to start with unit tests for your models to ensure your basic relationships and business logic are correct.
Create a test file for the User model:
php artisan make:test UserTest --unit
Write tests in tests/Unit/UserTest.php to verify the User-Post relationship:
assertTrue(true); } /** @test */ public function user_can_have_posts() { $user = User::factory()->create(); $post = Post::factory()->create(['user_id' => $user->id]); // Use the load method to refresh the posts relationship $user->load('posts'); $this->assertTrue($user->posts->contains($post)); } }
This test checks that a user can have associated posts.
In the context of Laravel's testing utilities, the assertDatabaseHas and assertDatabaseMissing methods are used to inspect the application's database and assert whether it contains or lacks certain data, respectively. These assertions are particularly useful for feature tests where you're testing the application's behavior as it interacts with the database. Here's a closer look at both methods:
The assertDatabaseHas method asserts that data exists in the database. This method is helpful when you want to ensure that a database operation, such as creating or updating a record, was successful.
Example:
Suppose you have a test that creates a post. To verify that the post was successfully created and exists in the posts table, you might use assertDatabaseHas like this:
$this->assertDatabaseHas('posts', [ 'title' => 'Example Post Title', 'content' => 'The content of the example post.', ]);
Conversely, the assertDatabaseMissing method asserts that specific data does not exist in the database. This method is useful for testing deletions or ensuring that a record was not created due to validation or authorization failures.
Example:
If you have a test that deletes a post, you can use assertDatabaseMissing to ensure that the post no longer exists in the posts table:
$this->assertDatabaseMissing('posts', [ 'id' => $postId, ]);
Feature tests simulate real user interactions with your application. Here, we'll write a test to ensure users can create and view posts.
Generate a test file for post interactions:
php artisan make:test PostTest
Write tests in tests/Feature/PostTest.php to simulate creating and viewing posts:
actingAs(User::factory()->create()); $postData = [ 'title' => 'Sample Post Title', 'content' => 'This is the content of the post.', ]; $this->post('/posts', $postData)->assertStatus(302); // Assuming redirection after creation $this->assertDatabaseHas('posts', $postData); } /** @test */ public function a_user_can_view_a_post() { $post = Post::factory()->create(); $this->get("/posts/{$post->id}") ->assertStatus(200) ->assertSee($post->title) ->assertSee($post->content); } }
// adjust our controller store post method : // method 1 $post = new Post(); $post->title = $request->input('title'); $post->content = $request->input('content'); $post->slug= Str::slug($post->title, '-'); $post->active = false; $post->user_id = auth()->id(); // or $request->user()->id; $post->save();
To run your tests, use the PHPUnit command:
php artisan test
Testing update and Delete
public function a_user_can_update_a_post() { $user = User::factory()->create(); $this->actingAs($user); $post = Post::factory()->create(['user_id' => $user->id]); $updatedData = [ 'title' => 'Updated Post Title', 'content' => 'Updated content of the post.', ]; $response = $this->put("/posts/{$post->id}", $updatedData); $response->assertStatus(302); // Assuming a redirect occurs after update $this->assertDatabaseHas('posts', [ 'id' => $post->id, 'title' => 'Updated Post Title', 'content' => 'Updated content of the post.', ]); } // Delete public function a_user_can_view_a_post() { $user = User::factory()->create(); $this->actingAs($user); $post = Post::factory()->create(['user_id' => $user->id]); $response = $this->get("/posts/{$post->id}"); $response->assertStatus(200); $response->assertSee($post->title); $response->assertSee($post->content); }
Laravel Telescope is an elegant debug assistant for Laravel applications. It provides insight into the requests coming into your application, exceptions, database queries, queued jobs, mail, notifications, cache operations, scheduled tasks, variable dumps, and more. Telescope is designed to make the development process easier by providing a convenient and powerful user interface to monitor your application's operations in real time.
To install Telescope, you typically run the following Composer command in your Laravel project:
composer require laravel/telescope
After installation, publish its assets and run the migrations:
php artisan telescope:install php artisan migrate
Once installed, Telescope can be accessed via your web browser at the /telescope path of your application (e.g., http://127.0.0.1:8000/telescope). Access to Telescope can and should be restricted in production environments to prevent unauthorized access to sensitive application data.
Laravel Telescope is an invaluable tool for Laravel developers, offering a rich set of features to improve application development, debugging, and performance optimization. Its user-friendly interface and comprehensive monitoring capabilities make it a must-have for serious Laravel projects.
First, you install the Laravel UI package using Composer:
composer require laravel/ui
This command adds the Laravel UI package to your project, allowing you to generate the basic scaffolding for authentication, as well as frontend presets for Vue.js, Bootstrap, or React.
Once Laravel UI is installed, you can generate the authentication scaffolding. If you're interested in using Vue.js along with the authentication system, you can do so with the following command:
php artisan ui vue --auth
This command does two main things:
After running the ui vue --auth command, you'll need to compile your assets (JavaScript and CSS) to reflect the changes in your application. You can do this with Laravel Mix by running:
npm install && npm run dev
php artisan migrate
now let adjust our redirect page after login and register in RouteServiceProvider :
public const HOME = '/home'; //to public const HOME = '/posts';
Laravel provides several methods through its Auth facade to access the currently authenticated user. Understanding these methods helps in managing user-specific data and enforcing access controls. Here are some examples:
Get the ID of the Authenticated User:
$userId = Auth::id();
This method returns the identifier of the authenticated user without loading the user model. It's useful when you only need the user's ID for database queries or logging.
Retrieve the Authenticated User Instance:
$user = Auth::user();
This method loads and returns the entire user model instance. It's helpful when you need more than just the user's ID, such as the user's name, email, or other attributes stored in the user table.
Accessing Specific Attributes of the Authenticated User:
$userEmail = Auth::user()->email;
After retrieving the user model instance, you can access any of its attributes. This example demonstrates how to get the email of the authenticated user.
Check If a User is Authenticated:
$isAuthenticated = Auth::check();
This method checks if the current visitor is authenticated. It returns true if the user is logged in and false otherwise. This is particularly useful for conditionally showing content or redirecting users.
Protecting routes is crucial to prevent unauthorized access to certain parts of your application. Laravel makes it straightforward to require authentication for specific routes using middleware.
use App\Http\Controllers\HomeController; use App\Http\Controllers\PostController; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Auth; // Public routes Route::get('/', function () { return view('welcome'); }); // Authentication Routes Auth::routes(); // Protected routes Route::middleware(['auth'])->group(function () { Route::get('/home', [HomeController::class, 'index'])->name('home'); Route::get('/about', [HomeController::class, 'about'])->name('about'); Route::resource('posts', PostController::class); });
Controller-based Middleware Protection:
use App\Http\Controllers\HomeController; use App\Http\Controllers\PostController; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Auth; // Public routes Route::get('/', function () { return view('welcome'); }); // Authentication Routes Auth::routes(); // Protected routes Route::middleware(['auth'])->group(function () { Route::get('/home', [HomeController::class, 'index'])->name('home'); Route::get('/about', [HomeController::class, 'about'])->name('about'); }); Route::resource('posts', PostController::class);
the constructor method of the PostController class is using $this->middleware('auth')->only(['create', 'edit', 'update', 'destroy']); to specify that only authenticated users are allowed to access the create, edit, update, and destroy actions. Here's a breakdown of what this does:
public function __construct() { $this->middleware('auth')->only(['create', 'edit', 'update', 'destroy']); }
If you want to apply middleware to all controller actions except for a selected few, you can use the except method instead of only. This is useful when most of your controller's actions require middleware, but a few public actions should remain accessible without it.
Here's how you can modify your constructor to use except:
public function __construct() { $this->middleware('auth')->except(['index', 'show']); }
Soft Deletes & Hard Deletes
To implement Soft Deletes and Hard Deletes in Laravel models like User and Post, you first need to understand what each type of deletion entails and then see how to apply them to these models.
Soft deleting is a way to "delete" a model without actually removing it from the database. Instead, a deleted_at timestamp is set on the model. This approach lets you recover the "deleted" models later if needed.
First, you need to create a new migration file for the table you wish to add soft deletes to. If you're modifying an existing table, you'll still create a migration but structure it to alter the table rather than create a new one. Use the Artisan CLI to create this migration:
php artisan make:migration add_soft_deletes_to_posts_table --table=posts
Open the newly created migration file in your editor. It will be named with a timestamp and the description you provided, something like 2024_01_01_000000_add_soft_deletes_to_posts_table.php.
Modify the up method to add a deleted_at column to the table. This column is used by Laravel to mark when a record was "deleted":
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class AddSoftDeletesToPostsTable extends Migration { public function up() { Schema::table('posts', function (Blueprint $table) { $table->softDeletes(); // Adds the deleted_at column }); } public function down() { Schema::table('posts', function (Blueprint $table) { $table->dropSoftDeletes(); // Removes the deleted_at column }); } }
Apply the migration to your database to alter the posts table by adding the deleted_at column:
php artisan migrate
To enable soft deletes in your Eloquent model, you must use the Illuminate\Database\Eloquent\SoftDeletes trait in the model that corresponds to the table you've just modified. Open or create the model file in app/Models/Post.php and update it as follows:
belongsTo(User::class); } public function comments() { return $this->HasMany(Comment::class); } }
$post = Post::find($postId); $post->delete();
To retrieve soft-deleted models, you can use the withTrashed() method on your model query:
$posts = Post::withTrashed()->get();
This approach ensures that when a Post model is deleted, all of its associated Comment models are also deleted, mimicking the behavior of a database-level ON DELETE CASCADE constraint.
// example without on cascade public static function boot(){ parent::boot(); static::deleting(function(Post $post){ $post->comments()->delete(); }); }
Hard deleting is the conventional way of deleting records from the database. Once a model is hard deleted, it's permanently removed from the database.
Hard deletes don't require any special setup in Laravel; they are the default behavior when you call the delete method on a model instance without using the SoftDeletes trait.
$post = Post::withTrashed()->find($postId); $post->forceDelete();
implements new logic to our Posts Controller :
// Existing soft delete method public function destroy(Request $request, string $id) { $post = Post::findOrFail($id); $post->delete(); // Soft delete $request->session()->flash('status', 'Post was deleted!'); return redirect()->route('posts.index'); } // Add this method for hard (force) delete public function forceDestroy(Request $request, string $id) { $post = Post::withTrashed()->findOrFail($id); $post->forceDelete(); // Permanently deletes the post $request->session()->flash('status', 'Post was permanently deleted!'); return redirect()->route('posts.index'); }
index.blade.php
@extends('layouts.app') @section('content')list of Post
{{session()->get('status')}}
@endif @forelse($posts as $post){{$post->content}}
{{$post->created_at}}No blog post yet!
@endforelseADD NEW ROUTE :
Route::delete('/posts/{id}/force', [PostController::class, 'forceDestroy'])->name('posts.forceDestroy');
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3