We recommend you read the Laravel docs in its entirety - even if it doesn't all make sense. You'll get a lay of the land and you'll note which things you do understand and which things you need some clarity and examples with. It's time well spent.
We need a way to show different "pages."
You can use a different file for each. But then you have to repeat the shared code like the header. Instead of creating a separate page-name.php file for every page, the typical approach is to use a single entry point that dynamically serves different HTML based on the requested URL. Think of it like how a Wi-Fi router directs internet traffic to the right device, a train track switch sends trains down the correct path, or an if-else statement decides what action to take.
There are many options. Check out the index.php
Using user requests to route to the correct "page" or template or vue.
Query strings rfc1738 were part of the original HTTP specification and are fundamental to how the web works, so - we're going to use them. They also help key the key:value pair thing visible and clear for discussion.
Based on what the user asks for, we can serve up the appropriate HTML.
We need to plan for when the user requests something that isn't there. Make sure you check out the browser developer tools network tab and check.
This file is looking scary! Lots of stuff jumbled together and it's only just a few pages so far.
We can create a "views" folder with all of our templates - and specifically a "pages" folder for page-level type templates.
There are so many ways you could do things. And abstracting out everything into patterns for the sake of it tends to confuse people when their learning - so much that they eventually get lost. So, we're going to take it as slow as possible.
If we abstract the header and footer out into their own partials, then the $query_page variable is just trusting that it is set somewhere further up the line.
Let's get this "router" into it's own file
So far, all of our files are public. If someone were to type into their URL bar /views/pages/home.php - it would render, even though that's not how our app is supposed to work. So, you could imagine, if you were to someone guess a URL and send along data with the request and manipulate what our code and computer are doing... that wouldn't be good.
We can reorganize a bit and point our server to the public/index.php file as the entry point. It seems like you could put all the secret app files in a secret folder and leave the index in the root, but it makes more sense to do the inverse. In MAMP you can just change the directory. In Herd you can use valet valet link my-project-name
(the folder / the thing.test)
Then, you can test it and see that the only entry point is the /public/index.php and nothing else can be accessed (unless from the program)
Note: Accessing /views/about.php directly won't throw a 404 because our router doesn't handle it yet. On a real server, anything outside /public is blocked by default.
Where are we now? A big more organizing
/my-project ├── /app │ ├── router.php │ ├── helpers.php ├── /views │ ├── /partials │ │ ├── header.php │ │ ├── footer.php │ ├── /pages │ │ ├── home.php │ │ ├── about.php │ │ ├── items.php │ │ ├── 404.php ├── /public │ ├── index.php ├── .gitignore ├── README.md
These end up getting organized visually in your file stystem or code editor - in ways that don't always seem like a clear story.
/my-project ├── README.md ├── /public │ ├── index.php ├── /app │ ├── router.php │ ├── helpers.php ├── /views │ ├── /partials │ │ ├── header.php │ │ ├── footer.php │ ├── /pages │ │ ├── home.php │ │ ├── about.php │ │ ├── items.php │ │ ├── 404.php ├── .gitignore
What if it were like this instead? A uses B uses C etc.? Try and keep things mapped in your brain. Maybe there's a way to create a config file that keeps then in a more intuitive order when things get 20x more complicated...
my-project ├── README.md # high-level project overview (this file!)
├── /public # entry point for all requests (images and css and things too) │ ├── index.php # entry point: calls router and helpers
├── /app # core logic and utilities │ ├── router.php # routes requests to the correct view │ ├── helpers.php # Common utilities (formatters, dump functions, etc.)
├── /views # presentation layer │ ├── /partials # shared template components │ │ ├── header.php # │ │ ├── footer.php # │ ├── /pages # specific page templates served based on routes │ │ ├── home.php # │ │ ├── about.php # │ │ ├── items.php # │ │ ├── 404.php # fallback for undefined routes
├── .configuration-files # many other configs end up here in the root ├── .gitignore # ignore unnecessary files in version control
You could get pretty far with just this. But let's pretend there's a database. And let's formalize the idea of "Item" in this case with an official Class. You could certainly use helpers, but we want to broach the MVCness of it all.
We can add some silly data and make a Item model. Why a model? Well, It's a way to encapsulate info and functions associated with how we access this type of data. The functions can share functions all in this scope.
Seems like we need a redirect() helper function since we're duplicating that code and it seems like something we're likely to screw up in so many places.
It might not seem necessary now, but views (like the item detail page) can get messy fast as they grow. If the view sticks to just the HTML and the model handles the data, then the "controller" acts as the go-between to keep things clean. The controller grabs and organizes data from the model and passes it to the view, keeping everything in its lane.
Let’s make a controller for the concept of "Item."
And while we’re at it, let’s talk about includes. Just like you don’t want a function spitting out output when it doesn’t know where it’s going, includes can cause trouble if they’re not controlled. Instead of just running a file and dumping its output, we can return the generated content as a string, giving us the flexibility to decide when and where it gets rendered.
This whole ob_start, include, ob_clean etc seem to happen a lot so, that would be a good thing to wrap up in a reusable function.
It might not seem necessary now, but views (like the item detail page) can quickly grow in complexity. If the view is focused purely on HTML and the model handles the data, then a "controller" acts as a bridge to keep the view clean. The controller fetches and organizes data from the model before passing it to the view. Let’s create one for the concept of "Item."
Just like we have a function to get our "views," we're going to need smaller views for things like the item-card to keep things organized and easy to work on. Let's make a component function for smaller bits that follows the same pattern. We can also create a page to show off all our components in one place.
├── /public # entry point for all requests (images and css and things too)
│ ├── index.php
├── /app # core logic and utilities
│ ├── router.php
│ ├── helpers.php
│ ├── /Models # domain logic and data abstraction
│ │ ├── Item.php
│ ├── /Controllers # handles request logic
│ │ ├── Item.php
├── /views # presentation layer
│ ├── /components # shared components
│ │ ├── item-card.php
│ ├── /partials # usually used just once
│ │ ├── header.php
│ ├── /pages # basically the biggest components
│ │ ├── page.php
(and remember - this is all just humans deciding this / there's not official "right" way)
There are so many patterns and concepts to try and show, but right now - everything seems pretty great. What else could you ask for!? You'd probably want to be able to upload new items, right? And we could do that by saving it to the JSON file, but that seems like a half-step. Instead, we can use sqlite and save to a real database.
We can create a database.sqlite
file in /data
and connect to it with a GUI of some sort. It'll be empty. You'll have to create a table. You can do it in a GUI or wih this sql query.
CREATE TABLE items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT NOT NULL,
symbol TEXT NOT NULL
);
You might need to refresh, but you'll see an items
table and an sqlite_sequence
table. Not sure what that one is for. Check it out.
Then we've got to make a form and hook that up. It's pretty complex! User puts in form data, submitting sends a POST request to the 'store' route which gets routed to the item controller's store action and then that checks out the info and gives it to the model's store action - and if that goes well, then the controller routes them somwhere. But that's where we'll leave it. It's not showing the real item data yet. You can check the database though, and if it works - it works.