Rants and Raves

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!

Pretty Some Things Up
2022-12-26
Spent some time on cleaning up the look of the site instead of just function. I am currently going to use Bootstrap to handle the styling, which requires the .ccs and .js lines of code added into your HTML. https://getbootstrap.com/docs/5.2/getting-started/introduction/ With that in place I played around with their navbar options. https://getbootstrap.com/docs/5.2/components/navbar/ I decided on a basic layout presented on W3Schools for a collapsible menu, about halfway down their page. https://www.w3schools.com/bootstrap5/bootstrap_navbar.php# I wanted to separate the main menu items from the login stuff, left and right side of page kind of stuff. That led me to find this post: https://stackoverflow.com/questions/19733447/bootstrap-navbar-with-left-center-or-right-aligned-items The main answer has a lot of good information and options to choose from. I ended up going with a small variation to his 3rd example that he linked: https://www.codeply.com/go/qhaBrcWp3v The main points that I took were to use 2 (or more) unordered lists (<ul>) inside of the navbar-collapse <div> then add: w-100 justify-content-start to the classes in the first <ul> and: ml-auto w-100 justify-content-end to the classes in the second <ul>. Now there can be a separation of menu items, but still have them collapse together on small screens. The last thing I did was add sticky-top to the classes of the <nav> at the very top of the navbar. Info on that is in the navbar link above. I went with the dark color theme for now, but you can check out the options here: https://getbootstrap.com/docs/5.2/customize/color/ The footer has less going on, so it didn't take quite as much to get it to match, and not go any higher than the bottom of the screen. For my example I used: https://getbootstrap.com/docs/5.0/examples/sticky-footer/ and ended up with: <html lang="en" class="h-100"> <body class="d-flex flex-column h-100"> <footer class="mt-auto py-3 bg-dark navbar-dark"> <div class="container navbar-nav"> <a class="nav-link" href="/">Home</a> </div> </footer> </body> </html> With the size of the navigation code, and anticipation of increased footer size, I thought it might be nice to give them their own view that my frame could grab. It is similar to the blade layouts that is being used, but is slightly different. For this we need to put: @include('layouts.navbar') @include('layouts.footer') in their respective places in the <body> HTML. This tells Laravel to display the code from the file given. It assumes the view folder by default, then you prepend and additional folders to the file name supplied. In the views folder there is a layouts folder with a navigation.blade.php file in it to include in the current code. https://laravel.com/docs/9.x/blade#including-subviews On the home page I cleaned up the content and gave it a more official welcome. I also removed the hello world stuff, fun for me to have from my beginning journey but not much use to others perusing the site. Some of the notable Adjustments to the blog area was switching from HTML tables to BS rows and columns: https://getbootstrap.com/docs/5.2/layout/columns/ Utilizing BS buttons: https://getbootstrap.com/docs/5.2/components/buttons/ Adding BS icons: https://icons.getbootstrap.com/ Adjusting some text characteristics: https://getbootstrap.com/docs/5.2/utilities/text/ Adjusting some spacing issues: https://getbootstrap.com/docs/5.2/utilities/spacing/ Tweaking the creation/edit form layout: https://getbootstrap.com/docs/5.2/forms/overview/ Requiring fields on the form: https://www.w3schools.com/tags/att_input_required.asp And reading up on the nuances of HTML margins: https://www.joshwcomeau.com/css/rules-of-margin-collapse/ I also modified the Breeze auth views so that they used BS form CSS and to use the view layouts for the rest of the site. This mainly involved adding: @extends('layouts.frame') to the top of the view, wrapping the view coding inside of: @section('content') Code @endsection and removing some slot elements: <x-guest-layout> <x-auth-card> </x-auth-card> </x-guest-layout> I've not dug into how slots work too much yet, but they do seem like the next step in blade layouts. https://laravel.com/docs/9.x/blade#slots I also noticed that on the phone, the pagination links of the blogs was too long. Inserted: ->onEachSide(0) into the blade code to help reduce the size. Although it makes it a little less navigable and needs further adjustment later. https://laravel.com/docs/8.x/pagination#adjusting-the-pagination-link-window
Better Form Element For Post Body
2022-12-25
I am almost to a point where I can go through and start correcting some of my posts for spelling and formatting. If you recall (if I said), the form that I was using had an <input type="text"> for the blog title and body, this did not leave a lot of space to actually see the body of the blogs. So I looked into expanding that space. What I ended up doing was trading the <input> element for the <textarea> element. From this: <input type="text" name="body" value="{{ $blog->body ?? '' }}"> To this: <textarea type="text" name="body" rows="4" style="width:100%;";>{{ $blog->body ?? '' }}</textarea> I did add the default styling to span the width of the window, as well as give me 4 rows of text to start with. The <textarea> element does come with the feature to allow the user to resize the area, and added bonus of allowing newlines. Now I'll really be able to see what is going into the DB after I paste the writing from Notepad, lol. https://www.w3schools.com/tags/tag_textarea.asp https://stackoverflow.com/questions/6007219/how-to-add-default-value-for-html-textarea
Create Post Meets Edit Post
2022-12-24
I took a lot of time to try to properly setup and use the controller's update function to modify a post. There is a discrepancy between HTML and Laravel in what methods are allowed to be used for a form. In Homestead if you use this command: php artisan route:list you will see a lot of useful information for all of the routes available, such as request methods, URIs, (shortcut) names, action (controller/function), and middleware. https://laravel.com/docs/9.x/routing#the-route-list The methods for the .update name are PUT or PATCH, which HTML does not like. So the solution is to add @METHOD('PUT') into your form in your view. https://laravel.com/docs/9.x/blade#method-field Laravel is supposed to use that to know to use the put method instead of the POST method for your form. The confusion comes with the action part of your form. For the standard POST method, the action of "/blog" will cause the route to use the store function in the controller. Adding the @METHOD('PUT') with the same action seems like it should then use the update function instead. However if you look at the URI section closer you might notice that there is no PUT or PATCH method for the /blog URI, it's actually for the /blog/{blog} URI. This means that your form's action needs to include the id of the blog you want to update. So in the Laravel documentation when they use /foo/bar what they are meaning is the /model/id. This does make sense when looking at the routes table, but it's something that seems to be heavily glossed over. https://stackoverflow.com/questions/69297746/nothing-any-update-with-put-method-in-laravel-8 With that big discussion out of the way, I'm going to go back to "I want to edit my posts". To start with, let's just add a link in the @foreach loop to add a link to the edit route, something like: @foreach($blogs as $blog) code stuff @can('gateLevel') <a href="/blog/{{ $blog->id }}/edit">Edit</a> @endcan other code @endforeach That link will pass the id of the post to the controller via the URI, and the route will take you to the edit function in the controller. In the edit function of the BlogsController I grabbed the information for the blog to pass into the view (hard to edit the post if you don't have the info available)by adding: $data['blog']=Blog::find($id); https://laravel.com/docs/9.x/eloquent#retrieving-single-models I also wanted to use the id in the title of the page so I concatenated the string and $id together with this: $data['title']="Edit Post $id"; Might want to brush up on the use of single vs. double quotes here: https://stackoverflow.com/questions/3446216/what-is-the-difference-between-single-quoted-and-double-quoted-strings-in-php#:~:text=In%20PHP%2C%20people%20use%20single,b%20%24c%20%24d%22%20.&text=echo%20%22my%20%24a%22%3B,for%20other%20used%20of%20string And PHP string/variable concatenation here: https://stackoverflow.com/questions/5605965/php-concatenate-or-directly-insert-variables-in-string After getting our data, let's pass it to the view we want to use with: return view('blogCreate')->with($data); I am opting to modify my creation view instead of having a completely new view. I did this because I thought it to be easier to edit just the one file and have to account for the different modes vs. having to make sure to remember to update 2 separate files when something changed with the information that is needed. Now in the blogCreate view I needed to alter my form a little bit with some ternary operators (condensed versions of if-then-else statements) https://www.codementor.io/@sayantinideb/ternary-operator-in-php-how-to-use-the-php-ternary-operator-x0ubd3po6 https://laravel.com/docs/5.1/blade#displaying-data Let's start with filling out the form with information from the blog we want to edit, we do this by adding a value attribute to each field and supplying it with the blog information. https://www.w3schools.com/tags/att_input_value.asp Since this view is also going to be used when creating and there is no blog information for those values, we cannot simply use something like: value="{{ $blog->title }}" otherwise it will error. Instead I used this: value="{{ $blog->title ?? '' }}" It'll use the data present in the variable if it exists, otherwise it will just be blank. To finish the view we need to do something similar for the action attribute in the form element as we did above. The action will look a bit like this: action="/blog/{{ $blog->id ?? '' }}" If there is a blog id (for an edit) it will append it to the URI, if not (for new) it will just be the base URI. From our discussion above, we also need to add the PUT method into the form if it's an edit. To do this we just need to wrap it in an @isset like so: @isset($blog->id) @METHOD('PUT') @endisset https://stackoverflow.com/questions/60777354/laravel-7-conditional-form-method-post-put https://laravel.com/docs/9.x/blade#if-statements There's still one more thing to worry about, the update function in the controller. I basically just copied the store function and put it into the update function and changed: $blog = new blog; to: $blog = Blog::find($id); If everything went well then after you sign in with you're account that can get through the gate, there should be an edit link for your posts, that should take you to a filled in post creation page, that will save any changes to the post back to the database. A bit of a bear this time, but I guess that's what happens when trying to merge different coding languages that have different capabilities.
HAPPY HOLIDAYS!
2022-12-23
'Tis the season for much merry making. Whatever, whenever, whoever you are doing, I hope you enjoyed it.
Make Use Of Gates
2022-12-22
Now that the groundwork for Authorization has been laid, it's time to put it to use. After some playing around I decided to mainly use the gates in the views and controllers (which makes me wonder if policies might have been the better way to go). Anyway, to use a gate in a controller it needs: use Illuminate\Support\Facades\Gate; added to the top of the file. For each of the controller methods that needs some authorization checks I wrapped the code of the function within this: if(Gate::any(['NameOfGate', ... ,'NameOfAnyOtherGates']){ Code } return redirect('/'); This way if a user doesn't have the permission it will just spit them back out to the main page. Next we can utilize the @can/@cannot blade directives with/instead of the @auth directives to manage what users can see in the views. Such as: @can('NameOfGate') HTML @endcan Another thing that is acessible with the Auth is user the current user info, although I think that the variable might need to be defined in the model to have access to it. In blade it looks a little like this: {{ Auth::user()->name }} I imagine it can also be used elsewhere by removing the curly brackets. With the gates in play in the controllers we no longer need the the basic auth middleware on the routes, so I removed: ->middleware(['auth']) from them. Keep in mind that this is not the only, and probably not best, way of using authentication/authorization. https://laravel.com/docs/8.x/authorization https://stackoverflow.com/questions/45257981/how-to-get-logged-in-user-data-into-laravel-controller
Create Default/Test Users
2022-12-21
With the authorization framework in place, I thought it would be a good idea to provide some default accounts for the different levels of authority. Partially for testing, and partially to make sure there is an admin account ready in production. I was able to follow a tutorial basically to the T to create an admin user here: https://maxkostinevich.com/blog/default-admin-user-in-laravel/ I did have a minor issue due to having a separate .env file for Homestead, but when I modified it instead it worked great. If you are also doing something like this, make sure to add that .env file to the .gitignore file to help protect any sensitive info from getting around. Due to the foreign references between the the user and the roles the tutorial was only a partial answer. In the seeder file we first need to change the code for the user to: $user = User::firstOrNew([ 'email' => config('admin.admin_email') ]); $user->fill([ 'name' => config('admin.admin_name'), 'password' => bcrypt(config('admin.admin_password')), ]); $user->save(); By using the firstOrNew function then we can modify the existing user with the ->fill() function followed by the ->save() function. After that will will create a similar section for the associated role, it should look a little like this: $role = Role::firstOrNew([ 'user_id' => $user->id ]); $role->fill([ 'yourRoleBool' => 1, ... 'otherNeededRoleBool' => 1, ]); $role->save(); You do not need to fill out all of the bools you have, just the ones you need, unless you are worried that some of the roles need removed. In that case you would also fill out all of the bools that need removed with a 0 instead of 1. While the role entry is complete, the user entry still doesn't know what role id to use. To finish it we will use this: $user->role_id = $role->id; $user->save(); This will finish assigning the role id to the user, and then save the change. There are other ways to do this, but at this point I am content with how this is working. https://laravel.com/docs/5.7/eloquent#other-creation-methods https://laravel.com/docs/5.7/eloquent#mass-assignment
Authorization Tweaks For Models And Gates
2022-12-20
I do believe that some of the things that we are doing to the Roles model and table are for reverse lookup type things, which will likely be necessary shortly if not as part of this. The main modifications for the next part are going to be in the Role.php and User.php Model, the AuthServiceProvider.php, and some testing in a view. In the User model we need to add: public function role() { return $this->hasOne(Role::class); } Likewise in the Role model we need to add: public function user() { return $this->belongsTo(User::class); } This allows Laravel to link the models together so that you can access the role information of a user just like it was a part of the user table. You will see that in the gates that we need to write in the AuthServiceProviders.php in the App/Providers directory. In the boot function under: $this->registerPolicies(); we need to add: Gate::define('nameForGate', function($user) { if($user->role){ return $user->role->roleColumnName; } }); Add one of those for each role column defined in the roles table. To test if it's working in a view add: @can('NameForCan') <p>Gate test of role</p> @endcan I did manually create/link/adjust the roles to test, but will work on using Laravel/the site to make those adjustments now that there is a proper authorization framework in place. https://stackoverflow.com/questions/51793094/users-role-use-gate-or-without https://www.itsolutionstuff.com/post/laravel-gates-and-policies-tutorial-with-exampleexample.html https://laravel.com/docs/9.x/authorization#gates https://laravel.com/docs/9.x/eloquent-relationships#one-to-one
Authorization Beginnings
2022-12-19
Working on Authorization now. There are a number of things to do, and keep in mind that this is only one of many ways to handle authorizations. I am planning on having a roles table that is linked to the users table. The roles table will have Boolean flags for what level/ability of access that user has. Then I plan to setup Gates that will be used to organize the user access. I took some time to clean up my ShowUsers.php code so that it was just users.php to get back to the spirit of the Laravel structure and take advantage of all of the controller abilities. You can just rename the model and controller files so long as you also rename ALL of the places where they are referenced. Not too hard so early on, but I did have a sneaky one in the routes file. The route was fine, it was the namespace at the top of the file that I forgot to rename. With that cleaned up We can start to dig into getting some roles worked out. Some quick commands to run in your project to get started are: php artisan make:model Role php artisan make:controller UserController -r (-r adds all of the default resources that Laravel can use) php artisan make:migration addRole To start with we need to adjust the database to have the table and fields ready for the app to play with. In the new addRole migration we need to make sure that the roles table is first made inside the up function with: Schema::create('roles', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('user_id'); $table->boolean('different roles here')->default(0); ... $table->timestamps(); $table->foreign('user_id')->references('id')->on('users'); }); Make sure to add as many Bool role types as you want, or add them later. For the bools I saw no difference between NULL and 0 so I went with defaults of 0. The foreign section is what binds the tables together and let's Laravel easily jump between them. Next in the up section the user table needs updated with: Schema::table('users', function (Blueprint $table) { $table->unsignedBigInteger('role_id')->nullable(); table->foreign('role_id')->references('id')->on('roles'); }); Once again the reference binds the tables together, which is why we needed to make sure the roles table was created first. Ultimately you would probably want all of your setup for each table in a single migration for itself, and then maybe a single migration just for the reference binding after the table creations. Otherwise just make a new migration and enter in what you need changed. Don't forget to add: Schema::dropIfExists('roles'); in the down function where needed. Once your migrations are ready, ssh into Homstead and run: php artisan migrate Database should be ready. I did consolidate my migration files before pushing to Bitbucket, and ran into a MySQL lock issue for the second foreign key creation. I decided to add: php sleep(2); in between the foreign keys to allow more time for the tables to unlock. I did test that the sleep function, didn't break migrations in Homestead and actually delayed, but I have yet to run it in production from Forge. https://www.folkstalk.com/2022/09/php-sleep-half-a-second-with-code-examples.html
Authentication At Work
2022-12-18
Authentication is now in play. Honestly it's a bit complicated to do from the ground up at this point, so I just tried Laravel's Breeze and... it was generically pretty easy to setup and works pretty well. https://laravel.com/docs/8.x/starter-kits#laravel-breeze Do note that this process will overwrite your routes file, so if you are trying to incorporate it into an existing project make sure to touch back on that. The basics of getting Breeze: CD into your project Run: composer require laravel/breeze:1.9.2 Run: php artisan breeze:install They do suggest "npm install" and "npm run dev" but I don't have npm and this already seemed to be working for me. For whatever reason my User model was just extending Eloquent and the Authenticatable extension was commented out. You're probably fine on that front, but be wary of any tweaks made to the User model while messing around. Currently your site's root will be to the Laravel welcome blade, but you will now also have access to /login /register /dashboard and authorization abilities. Now would be the time to correct/update your route file, I used this opportunity to add: ->middleware(['auth']) to my Show Users route. I also took this opportunity to surround my link to Show Users with: @auth code @endauth so it doesn't show if you're not logged in. Did that for adding a new blog, viewing every blog, as well as displaying some troubleshooting info. And of course some quick links to get to the authorization pages (that'll get cleaned up in a bit, like changing the application-logo.blade.php since the logo was ginormous for me).
Use MySQL To Update DB Entry
2022-12-17
If you find yourself using MySQL to try and update an existing entry it's not too hard. You will want a good piece of identifiable info such as id, and the column that you are adjusting. It'll look a little like this: update blogs set post_at='2022-12-11' where id = 111; https://www.mysqltutorial.org/mysql-update-data.aspx
Id Title Body Post At