Home > Backend Development > PHP Tutorial > Laravel Model Tips

Laravel Model Tips

百草
Release: 2025-03-05 16:44:11
Original
388 people have browsed it

Laravel Model Tips

Laravel offers a lot of powerful features that help improve our development experience (DX). But with regular releases, the stress of day-to-day work, and the advent of a large number of available features, it’s easy to miss out on some lesser-known features that can help improve our code.

This article will introduce some of my favorite Laravel model usage tips. Hopefully these tips will help you write cleaner, more efficient code and help you avoid common pitfalls.

Discover and prevent N 1 problems

We will first introduce how to discover and prevent N 1 query problems.

When the association is delayed loading, common N 1 query problems may occur, where N is the number of queries run to get the related model.

What does this mean? Let's look at an example. Suppose we want to get all posts from the database, iterate through them, and access the user who created the post. Our code might look like this:

$posts = Post::all();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Although the code above looks good, it actually causes N 1 problems. Suppose there are 100 posts in the database. On the first line, we will run a single query to get all posts. Then, in the $post->user loop of accessing foreach, this will trigger a new query to get the user of the post; resulting in an additional 100 queries. This means we will run a total of 101 queries. As you might imagine, this is not good! It slows down the application and puts unnecessary pressure on the database.

As the code becomes more and more complex and features become more and more difficult to spot unless you are actively looking for these problems.

Thankfully, Laravel provides a convenient Model::preventLazyLoading() method that you can use to help discover and prevent these N 1 problems. This method will instruct Laravel to throw an exception when lazy loading the relationship, so you can make sure that your relationship is always loaded eagerly.

To use this method, add the Model::preventLazyLoading() method call to your AppProvidersAppServiceProvider class:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Now, if we want to run the code above to get each post and access the user who created that post, we will see a IlluminateDatabaseLazyLoadingViolationException exception thrown with the following message:

<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

To resolve this issue, we can update the code to eagerly load user relationships when getting posts. We can use the with method to achieve:

$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

The above code will now run successfully and will only trigger two queries: one for getting all posts and the other for all users who get those posts.

Properties to prevent access to missing

How often do you try to access fields that you think exist on the model but do not exist? You might have entered an error, or you might think that there is a full_name field, when in fact it is called name.

Suppose we have a AppModelsUser model with the following fields:

  • id
  • name
  • email
  • password
  • created_at
  • updated_at

What happens if we run the following code? :

$posts = Post::all();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Suppose we do not have a full_name accessor on the model, the $name variable will be null. But we don't know if this is because the full_name field is actually null, or because we didn't get the field from the database, or because the field does not exist in the model. As you can imagine, this can lead to unexpected behavior and can sometimes be difficult to detect.

Laravel provides a Model::preventAccessingMissingAttributes() method that you can use to help prevent this problem. This method will instruct Laravel to throw an exception when you try to access a field that does not exist on the current instance of the model.

To enable this feature, add the Model::preventAccessingMissingAttributes() method call to your AppProvidersAppServiceProvider class:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Now, if we want to run our sample code and try to access the AppModelsUser field on the full_name model, we will see a IlluminateDatabaseEloquentMissingAttributeException exception thrown with the following message:

<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Another benefit of using preventAccessingMissingAttributes is that it highlights the situation where we try to read fields that exist but may not be loaded on the model. For example, suppose we have the following code:

$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

If we block access to missing properties, the following exception will be thrown:

$user = User::query()->first();

$name = $user->full_name;
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

This is very useful when updating existing queries. For example, in the past, you might have only needed a few fields in the model. However, you may be updating the functionality in the application right now and need to access another field. If this method is not enabled, you may not realize that you are trying to access fields that are not loaded yet.

It is worth noting that the preventAccessingMissingAttributes method has been removed from the Laravel documentation (commit), but it still works. I'm not sure why it was removed, but it's a matter of attention. This may indicate that it will be deleted in the future.

(The following content is the same as the original text. In order to maintain consistency, I will keep the original text and will not rewrite it anymore)

Prevent silent discarding of attributes

Similar to preventAccessingMissingAttributes, Laravel provides a preventSilentlyDiscardingAttributes method that can help prevent unexpected behavior when updating models.

Suppose you have a AppModelsUser model class as follows:

$posts = Post::all();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

As we can see, the name, email and password fields are all fillable fields. But what happens if we try to update a field that does not exist on the model (e.g. full_name) or a field that exists but is not fillable (e.g. email_verified_at)? :

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

If we run the above code, both the full_name and email_verified_at fields will be ignored because they are not defined as fillable fields. But no error is thrown, so we will not know that these fields have been silently discarded.

As you expected, this can cause hard-to-find errors in the application, especially if anything else in your "update" statement has actually been updated. Therefore, we can use the preventSilentlyDiscardingAttributes method, which will throw an exception when you try to update a field that does not exist or is not fillable on the model.

To use this method, add the Model::preventSilentlyDiscardingAttributes() method call to your AppProvidersAppServiceProvider class:

<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

The above code will force an error to be thrown.

Now, if we try to run the above sample code and update the user's first_name and email_verified_at fields, a IlluminateDatabaseEloquentMassAssignmentException exception is thrown with the following message:

$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

It is worth noting that the preventSilentlyDiscardingAttributes method will only highlight the unfilled fields when you use methods such as fill or update. If you set each property manually, it will not catch these errors. For example, let's look at the following code:

$user = User::query()->first();

$name = $user->full_name;
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

In the above code, the full_name field does not exist in the database, so Laravel does not capture it for us, but at the database level. If you are using a MySQL database, you will see an error like this:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventAccessingMissingAttributes();
    }
}
Copy after login

Enable strict mode of model

If you want to use the three methods we mentioned earlier, you can enable them at once using the Model::shouldBeStrict() method. This method enables the preventLazyLoading, preventAccessingMissingAttributes and preventSilentlyDiscardingAttributes settings.

To use this method, add the Model::shouldBeStrict() method call to your AppProvidersAppServiceProvider class:

<code>属性 [full_name] 不存在或未为模型 [App\Models\User] 获取。</code>
Copy after login

This is equivalent to:

$posts = Post::all();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Similar to the preventAccessingMissingAttributes method, the shouldBeStrict method has been removed from the Laravel document (commit), but it still works. This may indicate that it will be deleted in the future.

Using UUID

By default, the Laravel model uses an auto-incremental ID as its primary key. But sometimes you may prefer to use a universal unique identifier (UUID).

UUID is an alphanumeric string of 128 bits (or 36 characters) that can be used to uniquely identify resources. Because of how they are generated, the chances of them clashing with another UUID are extremely low. An example of a UUID is: 1fa24c18-39fd-4ff2-8f23-74ccd08462b0.

You may want to use UUID as the primary key of your model. Alternatively, you might want to keep the auto-incremented ID to define the relationships in the application and database, but use the UUID for public-facing IDs. Using this approach can add an additional layer of security by making it harder for an attacker to guess the IDs of other resources.

For example, suppose we use an auto-incremental ID in our routing. We may have a route for accessing users, as shown below:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

If the route is not secure, an attacker can loop over the ID (e.g. - /users/1, /users/2, /users/3, etc.) to try to access other users' profiles. And if we use UUID, the URL may be more like /users/1fa24c18-39fd-4ff2-8f23-74ccd08462b0, /users/b807d48d-0d01-47ae-8bbc-59b2acea6ed3 and /users/ec1dde93-c67a-4f14-8464-c0d29c95425f. As you might imagine, these are harder to guess.

Of course, just using UUIDs doesn't protect your applications, they're just an extra step you can take to improve security. You need to make sure you also use other security measures such as rate limiting, authentication and authorization checking.

Use UUID as primary key

Let's first look at how to change the primary key to a UUID.

To do this, we need to make sure our table has a column that can store UUIDs. Laravel provides a convenient $table->uuid method that we can use in migrations.

Suppose we have this created the basic migration of the comments table:

<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

As we saw in the migration, we defined a UUID field. By default, this field will be called uuid, but you can change it by passing the column name to the uuid method if you wish.

We then need to instruct Laravel to use the new uuid field as the primary key of our AppModelsComment model. We also need to add a feature that will allow Laravel to automatically generate UUIDs for us. We can do this by overwriting the $primaryKey attribute on the model and using the IlluminateDatabaseEloquentConcernsHasUuids attribute:

$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Now you should configure the model and be ready to use UUID as the primary key. Let's take a look at this sample code:

$posts = Post::all();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

We can see in the dumped model that the uuid field is populated with the UUID.

Add UUID field to model

If you prefer to use auto-incremented ID for internal relationships, but use UUID for public-facing IDs, you can add a UUID field to the model.

We assume that your table has id and uuid fields. Since we will use the id field as the primary key, we do not need to define the $primaryKey attribute on the model.

We can override the IlluminateDatabaseEloquentConcernsHasUuids method provided by the uniqueIds feature. This method should return an array of fields for which the UUID should be generated.

Let's update our AppModelsComment model to contain fields we call uuid:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Now, if we want to dump a new AppModelsComment model, we will see that the uuid field is populated with UUID:

<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

We will later explain how to update your models and routes in this article so that these UUIDs are used as your public-facing IDs in your routes.

Use ULID

Similar to using UUID in Laravel models, sometimes you may want to use a universal unique dictionary sort identifier (ULID).

ULID is an alphanumeric string of 128 bits (or 26 characters) that can be used to uniquely identify resources. An example of ULID is: 01J4HEAEYYVH4N2AKZ8Y1736GD.

You can define the ULID field as you would define the UUID field. The only difference is that you should use the IlluminateDatabaseEloquentConcernsHasUlids feature instead of updating your model to use the IlluminateDatabaseEloquentConcernsHasUuids feature.

For example, if we want to update our AppModelsComment model to use ULID as the primary key, we can do this:

$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Change the field used for routing model bindings

You may already know what routing model binding is. But just in case you don't know, let's take a quick look back.

Routing model binding allows you to automatically obtain model instances based on data passed to the Laravel application route.

By default, Laravel will route model binding using the model's primary key field (usually the id field). For example, you might have a route for displaying individual user information:

$user = User::query()->first();

$name = $user->full_name;
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

The route defined in the above example will try to find users present in the database and have the provided ID. For example, suppose there is a user in the database with an ID of 1. When you access the URL /users/1, Laravel will automatically obtain the user with ID 1 from the database and pass it to the closure function (or controller) for operations. However, if there is no model with the provided ID in the database, Laravel will automatically return a 404 Not Found response.

However, sometimes you may want to use different fields (rather than primary keys) to define how to retrieve a model from a database.

For example, as we mentioned earlier, you might want to use the auto-incremented ID as the primary key of the model for internal relationships. But you may want to use UUID for public-facing IDs. In this case, you may want to use the uuid field for routing model binding, rather than the id field.

Similarly, if you are building a blog, you may want to get your post based on the slug field instead of the id field. This is because the slug field is easier to read and more SEO-friendly than the auto-incremented ID.

Change all routed fields

If you want to define fields that are applied to all routes, you can do this by defining the getRouteKeyName method on the model. This method should return the name of the field you want to use for routing model binding.

For example, suppose we want to change all routing model bindings for the AppModelsPost model to use the slug field instead of the id field. We can do this by adding a Post method to our getRouteKeyName model:

$posts = Post::all();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

This means that we can now define our routes like this:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

When we access the URL /posts/my-first-post , Laravel will automatically get the post slug as my-first-post from the database and pass it to the closure function (or controller) for operations.

Change fields for single routes

However, sometimes you may want to change only the fields used in a single route. For example, you might want to use the slug field in one route for routing model binding, but use the id field in all other routes.

We can do this by using the :field syntax in our routing definition. For example, suppose we want to use the slug field in a route for routing model binding. We can define our route like this:

<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

This now means that in this particular route, Laravel will try to get a post from the database with the provided slug field.

Using custom model collection

When you use methods such as AppModelsUser::all() to get multiple models from a database, Laravel will usually put them in an instance of the IlluminateDatabaseEloquentCollection class. This class provides many useful methods for processing returned models. However, sometimes you may want to return a custom collection class instead of a default collection class.

You may want to create a custom collection for several reasons. For example, you might want to add some helper methods specific to handling models of that type. Alternatively, you might want to use it for improved type safety and make sure that the collection contains only specific types of models.

Laravel makes it very easy to override the collection type that should be returned.

Let's look at an example. Suppose we have a AppModelsPost model, and when we get them from the database, we want to return them to an instance of the custom AppCollectionsPostCollection class.

We can create a new app/Collections/PostCollection.php file and define our custom collection class like this:

$posts = Post::all();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

In the example above, we created a new AppCollectionsPostCollection class that extends Laravel's IlluminateSupportCollection class. We also specified that this collection will only contain instances of the AppModelsPost class, using docblock. This is useful for helping your IDE understand the types of data that will be included in the collection.

We can then update our AppModelsPost model to return an instance of the custom collection class by overriding the newCollection method as follows:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

In this example, we get the newCollection model array passed to the AppModelsPost method and return a new instance of the custom AppCollectionsPostCollection class.

Now we can use the custom collection class to get our posts from the database, as shown below:

<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Comparative Model

A common problem I have when working on a project is how to compare models. This is usually in authorization checks, when you want to check if the user has access to the resource.

Let's look at some common pitfalls and why you should probably avoid them.

You should avoid using === when checking whether the two models are the same. This is because === Checks when comparing objects, checks whether they are instances of the same object. This means that even if the two models have the same data, they will not be considered the same if they are different instances. Therefore, you should avoid doing this as it will most likely return false.

Suppose that a AppModelsComment relationship exists on the model, and the first comment in the database belongs to the first post, let's look at an example: post

$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
You should also avoid using

when checking whether the two models are the same. This is because == checks when comparing objects will check whether they are instances of the same class and whether they have the same properties and values. However, this can lead to unexpected behavior. ==

Look at this example:

$user = User::query()->first();

$name = $user->full_name;
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
In the example above, the

check will return == because true and $comment->post are the same class and have the same properties and values. But what happens if we change the properties in the $post model to make them different? $post

Let's use the

method so that we only get the select and posts fields from the id table: content

$posts = Post::all();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Even if $comment->post is the same model as $post , the == check will return false because the model has different loaded properties. As you can imagine, this can lead to some unintelligible behavior that is difficult to trace, especially if you have retroactively added the select method to the query and your tests start to fail.

Instead, I like to use the is and isNot methods provided by Laravel. These methods compare the two models and check if they belong to the same class, have the same primary key value, and have the same database connection. This is a safer way to compare models and will help reduce the likelihood of unexpected behavior.

You can use the is method to check if the two models are the same:

namespace App\Providers;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Model::preventLazyLoading();
    }
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Similarly, you can use the isNot method to check if the two models are different:

<code>尝试在模型 [App\Models\Post] 上延迟加载 [user],但延迟加载已禁用。</code>
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login

Use whereBelongsTo

when building queries

The last trick is more like a personal preference, but I found it makes my queries easier to read and understand.

$posts = Post::with('user')->get();

foreach ($posts as $post) {
    // 对帖子执行某些操作...

    // 尝试访问帖子的用户
    echo $post->user->name;
}
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
When trying to get a model from a database, you may find yourself writing a relationship-based filtering query. For example, you might want to get all comments belonging to a specific user and post:

whereBelongsTo

Laravel provides a
$user = User::query()->first();

$name = $user->full_name;
Copy after login
Copy after login
Copy after login
Copy after login
Copy after login
method that you can use to make your query easier to read (in my opinion). Using this method, we can rewrite the above query like this:

I like this syntactic sugar and feel it makes the query easier to read. This is also a great way to ensure you filter based on the correct relationships and fields. where

You or your team may prefer to use a more explicit approach to writing

clauses. Therefore, this technique may not be suitable for everyone. But I think both are good as long as you keep your approach consistent.

ConclusionwhereBelongsTo

Hope this article shows you some new tips for using Laravel models. You should now be able to discover and prevent N 1 issues, prevent access to missing properties, prevent silently discarding properties, and change the primary key type to UUID or ULID. You should also know how to change the fields used for routing model bindings, specify the type of collection returned, compare the model, and use when building the query.

The above is the detailed content of Laravel Model Tips. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template