Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
4,699 additions
and
4,715 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
## API | ||
|
||
⬆️ [Go to main menu](README.md#laravel-tips) ⬅️ [Previous (Log and debug)](Log-and-Debug.md) ➡️ [Next (Other)](Other.md) | ||
|
||
- [API Resources: With or Without "data"?](#api-resources-with-or-without-data) | ||
- [API Return "Everything went ok"](#api-return-everything-went-ok) | ||
- [Avoid N+1 queries in API resources](#avoid-N1-queries-in-API-resources) | ||
|
||
### API Resources: With or Without "data"? | ||
|
||
If you use Eloquent API Resources to return data, they will be automatically wrapped in 'data'. If you want to remove it, add `JsonResource::withoutWrapping();` in `app/Providers/AppServiceProvider.php`. | ||
|
||
```php | ||
class AppServiceProvider extends ServiceProvider | ||
{ | ||
public function boot() | ||
{ | ||
JsonResource::withoutWrapping(); | ||
} | ||
} | ||
``` | ||
|
||
Tip given by [@phillipmwaniki](https://twitter.com/phillipmwaniki/status/1445230637544321029) | ||
|
||
### API Return "Everything went ok" | ||
|
||
If you have API endpoint which performs some operations but has no response, so you wanna return just "everything went ok", you may return 204 status code "No | ||
content". In Laravel, it's easy: `return response()->noContent();`. | ||
|
||
```php | ||
public function reorder(Request $request) | ||
{ | ||
foreach ($request->input('rows', []) as $row) { | ||
Country::find($row['id'])->update(['position' => $row['position']]); | ||
} | ||
|
||
return response()->noContent(); | ||
} | ||
``` | ||
|
||
### Avoid N+1 queries in API resources | ||
You can avoid N+1 queries in API resources by using the `whenLoaded()` method.<br> | ||
This will only append the department if it’s already loaded in the Employee model.<br> | ||
Without `whenLoaded()` there is always a query for the department | ||
```php | ||
class EmplyeeResource extends JsonResource | ||
{ | ||
public function toArray($request): array | ||
{ | ||
return [ | ||
'id' => $this->uuid, | ||
'fullName' => $this->full_name, | ||
'email' => $this->email, | ||
'jobTitle' => $this->job_title, | ||
'department' => DepartmentResource::make($this->whenLoaded('department')), | ||
]; | ||
} | ||
} | ||
``` | ||
|
||
Tip given by [@mmartin_joo](https://twitter.com/mmartin_joo/status/1473987501501071362) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
## Artisan | ||
|
||
⬆️ [Go to main menu](README.md#laravel-tips) ⬅️ [Previous (Mail)](Mail.md) ➡️ [Next (Factories)](Factories.md) | ||
|
||
- [Artisan command parameters](#artisan-command-parameters) | ||
- [Maintenance Mode](#maintenance-mode) | ||
- [Artisan command help](#artisan-command-help) | ||
- [Exact Laravel version](#exact-laravel-version) | ||
- [Launch Artisan command from anywhere](#launch-artisan-command-from-anywhere) | ||
|
||
|
||
### Artisan command parameters | ||
|
||
When creating Artisan command, you can ask the input in variety of ways: `$this->confirm()`, `$this->anticipate()`, `$this->choice()`. | ||
|
||
```php | ||
// Yes or no? | ||
if ($this->confirm('Do you wish to continue?')) { | ||
// | ||
} | ||
|
||
// Open question with auto-complete options | ||
$name = $this->anticipate('What is your name?', ['Taylor', 'Dayle']); | ||
|
||
// One of the listed options with default index | ||
$name = $this->choice('What is your name?', ['Taylor', 'Dayle'], $defaultIndex); | ||
``` | ||
|
||
### Maintenance Mode | ||
|
||
If you want to enable maintenance mode on your page, execute the down Artisan command: | ||
```bash | ||
php artisan down | ||
``` | ||
|
||
Then people would see default 503 status page. | ||
|
||
You may also provide flags, in Laravel 8: | ||
- the path the user should be redirected to | ||
- the view that should be prerendered | ||
- secret phrase to bypass maintenance mode | ||
- status code during maintenance mode | ||
- retry page reload every X seconds | ||
|
||
```bash | ||
php artisan down --redirect="/" --render="errors::503" --secret="1630542a-246b-4b66-afa1-dd72a4c43515" --status=200 --retry=60 | ||
``` | ||
|
||
Before Laravel 8: | ||
- message that would be shown | ||
- retry page reload every X seconds | ||
- still allow the access to some IP address | ||
|
||
```bash | ||
php artisan down --message="Upgrading Database" --retry=60 --allow=127.0.0.1 | ||
``` | ||
|
||
When you've done the maintenance work, just run: | ||
```bash | ||
php artisan up | ||
``` | ||
|
||
### Artisan command help | ||
|
||
To check the options of artisan command, Run artisan commands with `--help` flag. For example, `php artisan make:model --help` and see how many options you have: | ||
|
||
``` | ||
Options: | ||
-a, --all Generate a migration, seeder, factory, and resource controller for the model | ||
-c, --controller Create a new controller for the model | ||
-f, --factory Create a new factory for the model | ||
--force Create the class even if the model already exists | ||
-m, --migration Create a new migration file for the model | ||
-s, --seed Create a new seeder file for the model | ||
-p, --pivot Indicates if the generated model should be a custom intermediate table model | ||
-r, --resource Indicates if the generated controller should be a resource controller | ||
--api Indicates if the generated controller should be an API controller | ||
-h, --help Display this help message | ||
-q, --quiet Do not output any message | ||
-V, --version Display this application version | ||
--ansi Force ANSI output | ||
--no-ansi Disable ANSI output | ||
-n, --no-interaction Do not ask any interactive question | ||
--env[=ENV] The environment the command should run under | ||
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug | ||
``` | ||
|
||
### Exact Laravel version | ||
|
||
Find out exactly what Laravel version you have in your app, by running command | ||
`php artisan --version` | ||
|
||
### Launch Artisan command from anywhere | ||
|
||
If you have an Artisan command, you can launch it not only from Terminal, but also from anywhere in your code, with parameters. Use Artisan::call() method: | ||
|
||
```php | ||
Route::get('/foo', function () { | ||
$exitCode = Artisan::call('email:send', [ | ||
'user' => 1, '--queue' => 'default' | ||
]); | ||
|
||
// | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
## Auth | ||
|
||
⬆️ [Go to main menu](README.md#laravel-tips) ⬅️ [Previous (Collections)](Collections.md) ➡️ [Next (Mail)](Mail.md) | ||
|
||
- [Check Multiple Permissions at Once](#check-multiple-permissions-at-once) | ||
- [More Events on User Registration](#more-events-on-user-registration) | ||
- [Did you know about Auth::once()?](#did-you-know-about-authonce) | ||
- [Change API Token on users password update](#change-api-token-on-users-password-update) | ||
- [Override Permissions for Super Admin](#override-permissions-for-super-admin) | ||
|
||
### Check Multiple Permissions at Once | ||
|
||
In addition to `@can` Blade directive, did you know you can check multiple permissions at once with `@canany` directive? | ||
|
||
```blade | ||
@canany(['update', 'view', 'delete'], $post) | ||
// The current user can update, view, or delete the post | ||
@elsecanany(['create'], \App\Post::class) | ||
// The current user can create a post | ||
@endcanany | ||
``` | ||
|
||
### More Events on User Registration | ||
|
||
Want to perform some actions after new user registration? Head to `app/Providers/EventServiceProvider.php` and add more Listeners classes, and then in those classes implement `handle()` method with `$event->user` object | ||
|
||
```php | ||
class EventServiceProvider extends ServiceProvider | ||
{ | ||
protected $listen = [ | ||
Registered::class => [ | ||
SendEmailVerificationNotification::class, | ||
|
||
// You can add any Listener class here | ||
// With handle() method inside of that class | ||
], | ||
]; | ||
``` | ||
|
||
### Did you know about Auth::once()? | ||
|
||
You can login with user only for ONE REQUEST, using method `Auth::once()`. | ||
No sessions or cookies will be utilized, which means this method may be helpful when building a stateless API. | ||
|
||
```php | ||
if (Auth::once($credentials)) { | ||
// | ||
} | ||
``` | ||
|
||
### Change API Token on users password update | ||
|
||
It's convenient to change the user's API Token when its password changes. | ||
|
||
Model: | ||
```php | ||
public function setPasswordAttribute($value) | ||
{ | ||
$this->attributes['password'] = $value; | ||
$this->attributes['api_token'] = Str::random(100); | ||
} | ||
``` | ||
|
||
### Override Permissions for Super Admin | ||
|
||
If you've defined your Gates but want to override all permissions for SUPER ADMIN user, to give that superadmin ALL permissions, you can intercept gates with `Gate::before()` statement, in `AuthServiceProvider.php` file. | ||
|
||
```php | ||
// Intercept any Gate and check if it's super admin | ||
Gate::before(function($user, $ability) { | ||
if ($user->is_super_admin == 1) { | ||
return true; | ||
} | ||
}); | ||
|
||
// Or if you use some permissions package... | ||
Gate::before(function($user, $ability) { | ||
if ($user->hasPermission('root')) { | ||
return true; | ||
} | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
## Collections | ||
|
||
⬆️ [Go to main menu](README.md#laravel-tips) ⬅️ [Previous (Validation)](Validation.md) ➡️ [Next (Auth)](Auth.md) | ||
|
||
- [Don’t Filter by NULL in Collections](#dont-filter-by-null-in-collections) | ||
- [Use groupBy on Collections with Custom Callback Function](#use-groupby-on-collections-with-custom-callback-function) | ||
- [Multiple Collection Methods in a Row](#multiple-collection-methods-in-a-row) | ||
- [Calculate Sum with Pagination](#calculate-sum-with-pagination) | ||
- [Serial no. in foreach loop with pagination](#serial-no-in-foreach-loop-with-pagination) | ||
- [Higher order collection methods](#higher-order-collection-methods) | ||
- | ||
|
||
### Don’t Filter by NULL in Collections | ||
|
||
You can filter by NULL in Eloquent, but if you're filtering the **collection** further - filter by empty string, there's no "null" in that field anymore. | ||
|
||
```php | ||
// This works | ||
$messages = Message::where('read_at is null')->get(); | ||
|
||
// Won’t work - will return 0 messages | ||
$messages = Message::all(); | ||
$unread_messages = $messages->where('read_at is null')->count(); | ||
|
||
// Will work | ||
$unread_messages = $messages->where('read_at', '')->count(); | ||
``` | ||
|
||
### Use groupBy on Collections with Custom Callback Function | ||
|
||
If you want to group result by some condition which isn’t a direct column in your database, you can do that by providing a closure function. | ||
|
||
For example, if you want to group users by day of registration, here’s the code: | ||
```php | ||
$users = User::all()->groupBy(function($item) { | ||
return $item->created_at->format('Y-m-d'); | ||
}); | ||
``` | ||
|
||
⚠️ Notice: it is done on a `Collection` class, so performed **AFTER** the results are fetched from the database. | ||
|
||
### Multiple Collection Methods in a Row | ||
|
||
If you query all results with `->all()` or `->get()`, you may then perform various Collection operations on the same result, it won’t query database every time. | ||
```php | ||
$users = User::all(); | ||
echo 'Max ID: ' . $users->max('id'); | ||
echo 'Average age: ' . $users->avg('age'); | ||
echo 'Total budget: ' . $users->sum('budget'); | ||
``` | ||
|
||
### Calculate Sum with Pagination | ||
|
||
How to calculate the sum of all records when you have only the PAGINATED collection? Do the calculation BEFORE the pagination, but from the same query. | ||
|
||
|
||
```php | ||
// How to get sum of post_views with pagination? | ||
$posts = Post::paginate(10); | ||
// This will be only for page 1, not ALL posts | ||
$sum = $posts->sum('post_views'); | ||
|
||
// Do this with Query Builder | ||
$query = Post::query(); | ||
// Calculate sum | ||
$sum = $query->sum('post_views'); | ||
// And then do the pagination from the same query | ||
$posts = $query->paginate(10); | ||
``` | ||
|
||
### Serial no in foreach loop with pagination | ||
We can use foreach collection items index as serial no (SL) in pagination. | ||
|
||
```php | ||
... | ||
<th>Serial</th> | ||
... | ||
@foreach ($products as $product) | ||
<tr> | ||
<td>{{ $loop->index + $product->firstItem() }}</td> | ||
... | ||
@endforeach | ||
``` | ||
it will solve the issue of next pages(?page=2&...) index count from continue. | ||
|
||
### Higher order collection methods | ||
|
||
Collections have higher order methods, this are methods that can be chained , like `groupBy()` , `map()` ... Giving you a fluid syntax. This example calculates the | ||
price per group of products on an offer. | ||
|
||
```php | ||
$offer = [ | ||
'name' => 'offer1', | ||
'lines' => [ | ||
['group' => 1, 'price' => 10], | ||
['group' => 1, 'price' => 20], | ||
['group' => 2, 'price' => 30], | ||
['group' => 2, 'price' => 40], | ||
['group' => 3, 'price' => 50], | ||
['group' => 3, 'price' => 60] | ||
] | ||
]; | ||
|
||
$totalPerGroup = collect($offer->lines)->groupBy('group')->map(fn($group) => $group->sum('price')); | ||
``` |
Oops, something went wrong.