Welcome to Rants and Raves!
I kept hearing that I needed to write, so here is the blog that I ended up starting.
It is mostly an accounting of my progression through creating a web site, nuances with code, and tips about what I did to make things work.
In there you will find sprinkled about some gems of life, and letting loose fun.
Enjoy!
Gates And Route Parameters
2023-01-14
Sometimes you may want to use route parameters in your gates to compare against a users access to that thing. For example you might have users that hasMany(Departments::class) and you want to check if the user is part of the department when they try to access that page, /departments/3. If the route is defined using resource(), then Laravel will automatically define that route and parameter as /departments/{department}. when making gates you can access this parameter using request()->department which you can use in a check such as:
if($user->department->id == request()->department){
return TRUE;
}
https://stackoverflow.com/questions/70898202/laravel-how-to-pass-additional-parameters-to-gate-keeping-their-original-types
If you would also like to change the route parameter to something else, perhaps to make it a bit smaller, you can by using ->parameters([]) on the route:
Route::resource('department', DepartmentsController::class)->parameters([
'department' => 'dept',
]);
https://laravel.com/docs/9.x/controllers#restful-naming-resource-route-parameters
This comes in handy when you want to use "dot" notation for nested resources.
https://laravel.com/docs/9.x/controllers#restful-nested-resources
Route::resource('departments/{dept}/users', DeptUsersController::class);
{dept} and {user} will be the route parameters here.
Route::resource('departments.users', DeptUsersController::class);
{department} and {user} will be the route parameters here.
Route::resource('departments.users', DeptUsersController::class)->parameters([
'department' => 'dept',
]);
{dept} and {user} will be the route parameters here.
Relationship Column Selection And Nav Tab Returning
2023-01-13
Earlier I showed how to limit the results of a hasMany relation on a model by using advanced where clauses. Well, found another situation that seems to need them, but it required more than anticipated. This time I only wanted to select certain fields of the model and of the relation that it was grabbing. Found that you do need to include the 'id' of the relation to select with otherwise Laravel doesn't know which one to grab.https://stackoverflow.com/questions/38172857/how-to-select-specific-columns-in-laravel-eloquent
https://laracasts.com/discuss/channels/eloquent/eloquent-select-field-with-relationship?page=1
I ended up with something like this:
$model = Model::where('id', $id)
->with([
'relationship' => function($q) {
$q->select('id', 'column1', 'column2', ...);
}])->get();
Another thing I wanted to do was to have the same tab selected after the page loaded after a form submittal. I think there is some mention of using cookies, but I ended up using the session data (I think). It was more or less just to pass a little data between controllers. Started with a hidden form value:
<input hidden name="tab_name" value="Tab1">
Then in the controller called by the form submit, grab the data and pass it into the return:
$data['tab_name'] = $request->tab_name;
return redirect()->back()->with($data);
Then I grabbed it again in the controller that the redirect was referencing to pass it onto the view:
$data['tab_name'] = session()->get('tab_name') ?? '';
Now checks in the nav-tabs, first with the nav-links, then with the tab-pane:
{{ empty($tab_name) || $tab_name == 'Tab1' ? 'active' : '' }}
That is for the tab that is going to be the default, all the rest would && against a not empty:
{{ !empty($tab_name) && $tab_name == 'Tab2' ? 'active' : '' }}
As I write this I get the feeling that I have implemented the session data inefficiently and that there might be a better way to go about what I was looking to do. Oh, well.
https://stackoverflow.com/questions/25078452/how-to-send-data-using-redirect-with-laravel
https://laravel.com/docs/9.x/helpers#method-session
https://stackoverflow.com/questions/33747262/redirect-back-to-a-specific-tab-pane-in-laravel
https://stackoverflow.com/questions/38482742/redirect-url-to-previous-page-in-laravel
A Little Note On Forge, DigitalOcean, And Subdomains
2023-01-12
A little side note, Forge/Digitalocean can also handle sub domains. For forge the setup is as easy as just making a site that has the site domain that is the full sub domain name:
sub.site.com
For DO you just need to go into your domain network area, manage the domain you are interested in, and make a new entry by supplying just the sub domain name. DO will append the rest of the domain name and make the record for you.
https://www.datanovia.com/en/lessons/how-to-create-a-subdomain-on-digitalocean/#step-1-create-an-a-record-for-the-subdomain
This does open up a lot of possibilities, but for now I think I will hold off.
No Plows For Truck Lots?
2023-01-11
It is interesting seeing some of the differences between being in a truck or not. Right now I am watching as many truck struggle to get traction in a parking lot that looks to not have been plowed. There is ice, slush, giant potholes of water, hills of packed snow, and all kinds of slipping and sliding that is very troublesome to get around; let alone precision maneuver 65 feet of 80,000 lbs of truck and trailer backwards into a parking spot that's not much wider than your big pickup. All while 100 feet away there's a nicely plowed parking area for personal vehicles which eliminates any worry of getting stuck or bottoming out. That is definitely good for smaller vehicles, but these truck are definitely struggling really badly. Yes, they are big and powerful. But they aren't designed for 4-wheeling. It would be nice if the people who are busy trying to make sure everybody gets all their consumer products were treated the same as those that are consuming.
Domains: Forge, DigitalOcean, And Google
2023-01-10
To make websites easier to find the internet imploys Domain Name System (DNS) servers. This is basically a system to translate the nice website names we use into the IP (Internet Protocol) address that computers use to transfer data back and forth. While you don't need need a domain, as you can directly enter an IP address to go there, it does make it a lot easier for everyone to use domains instead.
https://en.wikipedia.org/wiki/Domain_Name_System
Bitter sweetly you can't just use absolutley any domain name you want, it has to be available in the DNS library... and purchased for yourself. I finally found and settled on a domain for this site to use, jaredgarrison.com. While I went with Google for my DNS Registrar because I am already using them for other things, there are a number of different ones.
https://www.icann.org/en/accredited-registrars
Now to make Forge, DigitalOcean, and Google Domains play together.
First off, your DNS registrar needs to be updated to point to the DO DNS servers. DO has a nice article on how to do this for a number of registrars:
https://docs.digitalocean.com/tutorials/dns-registrars/
I am assuming at this point you have already picked a regisrar and purchased your domain name. Like a I said, I used Google, so I will use tthat as an example. In your domains.google.com account you will want to:
Select 'My domains" > Click "Manage" next to your domain > Select "DNS" on the left > select "Custom name servers" > Enter the 3 "ns#.digitalocean.com" addresses in the name server areas (note that your will need to hit the + link to get the 3rd in there) > Save
Now the DNS will use DO for the records instead of the registrar. You could enter the record info into the regisrar instead of using DO, but I think if DO ever changes its IP, or your droplet's IP, it will update the record automatically. Otherwise, when a change like that happens, you would have to go in and update your registrar manually. Also note that registrar updates might take a while to propogate through the internet, so you might need to be patient and maybe attempt these changes when downtime won't be as much of a problem.
Next in DigitalOcean you will also need to setup the domain records, as DO actually acts as a DNS server. Don't worry, its pretty easy. There are two ways that I know of to get to the setup:
DO > Manage > Networking > Domains
DO > Droplets > More dropdown next to droplet > Add a domain
From here you just need to enter your domain name, such as jaredgarrison.com, make sure the correct droplet is selected and then hit "Add Domain". DO then creates the necessary records for the DNS server.
Lastly, your site in Forge needs to use the same Site Domain as the one you can get with a registrar. Ideally this should be done when you make the site in Forge, however you can adjust an existing site if you need to. If you already have a Forge site and want to adjust the settings to match, the important one is at:
Forge > Site > Meta > Domain > Site Domain
A second, but potentially less important depending on your setup is:
Forge > Site > Environment > Site Environment > APP_URL
I believe if these are adjusted to your appropriate site domain you should be good. Note that there might be a possibility of needing the "default" site to no longer be in play. I had removed it when I was working on getting my domain setup, but have since put it back and it doesn't sem to be hurting anything yet.
If everything went well, you should be able to access your Forge/DO site through your freshly claimed domain name.
Route Groups And Partial Resources
2023-01-09
Cleaning up the routes table after getting the verification email working ended up having 2 major components. The first was to see if I could attach the middleware to a bunch of routes at once. And the second was to see if I one of the functions of a resource route could be excluded from the middleware. The first ended up being pretty easy, it was to use route groups.
https://laravel.com/docs/9.x/routing#route-groups
Route::middleware(['first', 'second'])->group(function () {
Route::get('/', function () {} );
Route::get('/user/profile', function () {} );
});
The second point was a bit more tricky. Consider this:
Route::get('/blog', [BlogsController::class, 'index']);
Route::middleware('verified')->group(function(){
Route::resource('/blog', BlogsController::class);
});
Based on Laravel's documentation on supplementing resource controllers, this should find and run the "get" route before the "resource" route. However the middleware still applies, since "get" is still defined in the "resources". The solution for me was to define a partial resource route:
https://laravel.com/docs/9.x/controllers#restful-partial-resource-routes
Route::get('/blog', [BlogsController::class, 'index']);
Route::middleware('verified')->group(function(){
Route::resource('/blog', BlogsController::class)->except('index');
});
This prevents the resource from manipulating the given controller action, and not apply the middleware to the route that I'm not concerned about having the middleware on. In my researches I found a good page that talks all about how Laravel's routing works, and was the first to point me toward the partial resource routes:
https://www.codemag.com/Article/2301041/Mastering-Routing-and-Middleware-in-PHP-Laravel
Laravel Email Verification Fixing
2023-01-08
Since setting up Laravel Breeze email verification wasn't working, no email was being sent. Breeze seems to have missed that as a required setup step, which makes sense since you seem to need to utilize an external email server. To start off use Laravel's documentation for setting up verification protection:
https://laravel.com/docs/8.x/verification
I needed to add the "implements MustVerifyEmail" on the user model, set up the verification routes, and protect a route with the "->middleware('verified')" modifier. That's when I finally was kicked out of a portion of my test site, which also brought me to the verify email page. Still, pressing the send verification button did not result in any email being sent. Why should I expect anything different? Laravel is not an email server, and there were no settings changed to use anything. I opted for Gmail, as that is what I use anymore, and the SMTP mode for the time being. Google has increased its security and no longer allows the toggle to allow less secure apps to use your email. Instead I had to setup 2-step verification in Google, and then setup an "App Password". You will use this password instead of your normal login password.
https://support.google.com/accounts/answer/185833
You will also want to add the following in your .env file:
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=465
MAIL_ENCRYPTION=ssl
MAIL_USERNAME=YourEmail@gmail.com
MAIL_PASSWORD=16CharacterAppPassword
Information from Google found here:
https://support.google.com/mail/answer/7126229?hl=en#zippy=%2Cstep-check-that-imap-is-turned-on%2Cstep-change-smtp-other-settings-in-your-email-client
Note that I was able to get successful emails without enabling IMAP.
Second note, when transferring to Forge, be sure to delete or overwrite their default mailer info that is lower in their site environment file. Otherwise you might get an error about not being able to connect to mailhog.
Migration Nuances, Relationship Filtering, And Arrays As Variables
2023-01-07
Fun deal, when using the renameColumn() in a migration, the foreign key attached to it also gets renamed. Or at least that is what it seemed when I ran the change in production, kind of broke the rest of the migration that was supposed to fix the foreign key.
https://laravel.com/docs/9.x/migrations#renaming-columns
If you are doing gate checks not in the views, and are only concerned with one check, be sure to use allows() instead of any().
https://laravel.com/docs/9.x/authorization#authorizing-actions-via-gates
Something that seems a bit easier than apparently it is, filtering the relations that are gathered when adding the ->with() modifier. I did not want to pull all of the relations that a model had, but rather a sub-group of them. I ended up using advanced where clauses to do what I needed.
https://laravel.com/docs/master/queries
I ended up with something like this:
$model = Model::where('id', $id)
->with([
'relationship' => function($q) use ($filter) {
$q->where('relationship.key_to_filer_on', $filter);
}])->first();
Note the use of use() in the function, that is what allows you to pass an outside variable into your function.
https://stackoverflow.com/questions/29548073/laravel-advanced-wheres-how-to-pass-variable-into-function
A note on using arrays to provide variable parameters as part of an object path (?), they don't play nice. You can certainly use them by themselves, but more difficult in place of other things. For example, in a controller:
foreach( $array_items as $item){
$item_value = $item['key']; //works
$var->sub_item->$item['key'] = 'new value'; //doesn't work
$var->sub_item->$item_value = 'new value'; //works
$var[$item_value]; //works
$var[$item['key']]; //works... I think
endforeach
I guess long story short for using arrays as variables in conjunction with something else, you may need to actually assign each key/value pair to a variable to use it. And in a view you will likely need to wrap it in an @php @endphp.
Daily Goods
2023-01-06
Had some good chili.
Watched some One Peice.
Got chores done early.
I like that McKendra likes a wide variety of music.
I like that Jared enjoys Legos.
I'm glad that cats exist.
Getting the towel tucked into the little cubby made me happy.
I am good enough as is.
Follow Laravel's Naming Convention
2023-01-05
When dealing with Laravel's implied system it is important to make sure things are named in a particular way. I just ran across an issue where I did not fully follow their convention, and then I couldn't use belongsTo(). I wanted to have a "make and model" type situation, but I was not allowed to use "model" as a model, since they already have a "model" as a model. So I decided to use "design" as the model and database name. The problem was that I decided to try and keep things clearer elsewhere and still use model for some of my other database relations. So when trying to use the belongsTo() method, it wouldn't work because it couldn't find a design_id because I had a model_id instead. You might not normally run into that situation, the only reason I did was because I couldn't use "model for a model.