diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index c9f8a323..d77924f9 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -73,17 +73,42 @@ All code snippets should use triple backticks with language specified for syntax
### Images and Links
-**CRITICAL - Image Paths**: Images are stored in `app/pages/{version}/images` alongside the markdown files, and **MUST always use absolute paths starting with `/`**.
-- ✅ Correct: ``
-- ❌ Wrong: `` or ``
+**CRITICAL - Image Paths**: Images are stored in `app/pages/{version}/images/` alongside the markdown files, and **MUST always use a path relative to the _version_ folder, _without_ a starting `/`**.
+- ✅ Correct: ``
+- ❌ Wrong: `` or ``
Images should be checked to ensure the file exists at the specified path.
-Internal links should use absolute paths without version numbers to allow automatic version switching.
-- ✅ Correct: `[Requirements](/installation/requirements)`
-- ❌ Wrong: `[Requirements](installation/requirements)` or `[Requirements](/04.installation/01.requirements)`
+Internal links should use relative paths to the version folder, without version numbers to allow automatic version switching.
+- ✅ Correct: `[Requirements](installation/requirements)`
+- ❌ Wrong: `[Requirements](/installation/requirements)` or `[Requirements](04.installation/01.requirements)`
Pages have anchor links for sections (e.g., `#motivation`). Ensure links point to existing sections.
+### Alert Syntax
+
+Alerts can be used to emphasize critical information or provide tips to the user. They are displayed with distinctive colors and icons to indicate the significance of the content.
+
+Use alerts only when they are crucial for user success or to add helpful information. Additionally, you should avoid placing alerts consecutively. Alerts cannot be nested within other elements.
+
+Five types of alerts are available:
+
+```
+> [!NOTE]
+> Highlights information that users should take into account, even when skimming.
+
+> [!TIP]
+> Optional information to help a user be more successful.
+
+> [!IMPORTANT]
+> Crucial information necessary for users to succeed.
+
+> [!WARNING]
+> Critical content demanding immediate user attention due to potential risks.
+
+> [!CAUTION]
+> Negative potential consequences of an action.
+```
+
### Chapters and Pages
Documentation pages are stored in `docs.md` or `chapter.md` files under `app/pages/{version}/` (e.g., `app/pages/6.0/01.quick-start/docs.md`).
diff --git a/app/pages/6.0/02.background/01.introduction/docs.md b/app/pages/6.0/02.background/01.introduction/docs.md
index a41ed367..9e6200a6 100644
--- a/app/pages/6.0/02.background/01.introduction/docs.md
+++ b/app/pages/6.0/02.background/01.introduction/docs.md
@@ -9,7 +9,7 @@ The PHP community has evolved considerably over the past two decades, beginning
This breakneck pace has caused a lot of people to get left behind. For someone who hasn't been doing web development continuously for the past decade or more, it can feel like an overwhelming task to get acquainted with all of the new tools and frameworks that seem to be coming out every day. Relevant comic from [Abstruse Goose](http://abstrusegoose.com/503):
-
+
The problem is that when you're a busy developer with real-world projects to work on, it's very difficult to set aside time to read a book about technology X - especially when you're not even sure that you really _need_ to learn X!
diff --git a/app/pages/6.0/02.background/02.the-client-server-conversation/docs.md b/app/pages/6.0/02.background/02.the-client-server-conversation/docs.md
index eddd8aff..4e50bc53 100644
--- a/app/pages/6.0/02.background/02.the-client-server-conversation/docs.md
+++ b/app/pages/6.0/02.background/02.the-client-server-conversation/docs.md
@@ -1,12 +1,11 @@
---
title: The Client-Server Conversation
description: Many developers do not really understand the basics of how HTTP and web applications work. This discussion attempts to clarify some common misconceptions.
-obsolete: true
---
One of the most common misconceptions is that web applications are coherent pieces of software that sit on a server somewhere, and that the client "runs" this application in their browser. This is actually an illusion, carefully crafted to provide a smooth experience for the end user.
-In reality, web applications are *conversations* between two agents with very poor memory - the **server**, and the **client** - which may be a web browser, mobile app, or another application. In modern web applications both the client *and* the server are typically going to need to run some code throughout their conversation. What's more, in the case of a PHP application, the client and server don't even speak the same language! The server runs only PHP, while the client runs only Javascript. (Note that there *are* server-side Javascript stacks, but we do not use them.)
+In reality, web applications are *conversations* between two agents with very poor memory - the **server**, and the **client** - which may be a web browser, mobile app, or another application. In modern web applications both the client *and* the server are typically going to need to run some code throughout their conversation. What's more, in the case of a PHP application, the client and server don't even speak the same language! The server runs PHP (via UserFrosting's Slim-based backend), while the client runs JavaScript (often with frameworks like Vue). Note that there *are* server-side JavaScript stacks, but UserFrosting is built on PHP.
## Server-side versus client-side code
@@ -32,60 +31,53 @@ You don't need to do anything. Your web server is configured to do this automati
> How do I get PHP in my Javascript?
-Again, clients can't run PHP in their browsers. If you want to pass along the values of some PHP variables to the Javascript you send back to the client, you need to explicitly *generate* Javascript variables using PHP. For example:
-
-```html
-
+Again, clients can't run PHP in their browsers. If you want to pass along the values of some PHP variables to the JavaScript you send back to the client, you need to explicitly *generate* JavaScript variables using your templating engine. UserFrosting uses **Twig** for this. For example:
+```twig
```
-UserFrosting has a cleaner way of doing this using the Twig templating engine, but the principle is still the same.
+This uses PHP syntax to inject server-side values into the client-side JavaScript. It can also be done using Twig's `{{ }}` syntax. The templating happens server-side before the HTML is sent to the browser.
For more complex PHP code that needs to be run in the middle of a block of Javascript code (for example, querying the database, which can *only* be done server-side), we'll need a way to let Javascript code ask the server to run some code on its behalf. Remember, the only way we can run code on the server is by making requests and then waiting for a response!
-Fortunately, modern browsers support something called AJAX, which allows Javascript code to automatically make requests to the server. Thus, you might see something like:
-
-```php
-
+Fortunately, modern browsers support something called AJAX (Asynchronous JavaScript and XML), which allows JavaScript code to make requests to the server without reloading the page. Applications typically use the modern `fetch()` API for this. For example:
+```twig
```
+> [!NOTE]
+> For more complex interactions, UserFrosting supports **Vue 3** components, which provide a more structured way to build reactive user interfaces. Vue components handle state management, reactive data binding, and component lifecycle automatically, making it easier to build sophisticated client-side features. You'll learn more about Vue integration in the [Asset Management](/asset-management) chapter.
+
### The Big Picture
Let's take a step back and talk about what's really going on here.
@@ -94,9 +86,9 @@ Let's take a step back and talk about what's really going on here.
*requests a dynamically generated resource from the server...*
-*which contains some HTML and Javascript...*
+*which contains some HTML, JavaScript, CSS, and references to other assets...*
-*that Javascript contains instructions for the user's browser...*
+*that JavaScript contains instructions for the user's browser...*
*one of which is to automatically ask the server for some JSON data whenever the user presses a certain button...*
@@ -114,13 +106,13 @@ It might be easier to understand this whole process if we provide an example of
**owlfancy.com:** "Sure. Looks like for that resource, I'm supposed to run this bit of code over here. Let's see what happens when I do that...Ok, it's done! Looks like it returned some HTML. Here you go. The status code is 200. Let me know if you need anything else. Bye!"
-**Your browser:** "Hmm, according to this, I'm supposed to ask you for *jquery.js*, *bootstrap.js*, *pellet.js*, *bootstrap.css*, and something called */images/preening.jpg*. Can I have those as well please? I'm also supposed to get *https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic*, but that's not your problem. I can ask *fonts.googleapis.com* myself.
+**Your browser:** "Hmm, according to this, I'm supposed to ask you for some JavaScript bundles (*app.js*, *vendor.js*), some CSS (*theme.css*), and something called */images/preening.jpg*. Can I have those as well please? I'm also supposed to get *https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic*, but that's not your problem. I can ask *fonts.googleapis.com* myself.
**owlfancy.com: (for each requested item):** "Sure, here you go. Let me know if you need anything else. Bye!"
-**Your browser:** "Ok, now I'll run all this CSS and Javascript code." *Runs jquery.js, bootstrap.js, and pellet.js*
+**Your browser:** "Ok, now I'll run all this CSS and JavaScript code." *Runs app.js and vendor.js*
-**Your browser:** "Hmm, looks like I'm supposed to give these boxes over here a border and shadow, change the font for this to Lora, and set it up so that when someone clicks "Forage", a picture of an owl swoops across the screen. Oh, it also wants me to tell Google Analytics and Facebook what I just did. Sure, after all my owner didn't tell me *not* to do that...I'll just send that information right now.
+**Your browser:** "Hmm, looks like I'm supposed to give these boxes over here a border and shadow, change the font for this to Lora, mount some Vue components, and set it up so that when someone clicks "Forage", a picture of an owl swoops across the screen. Oh, it also wants me to tell Google Analytics and Facebook what I just did. Sure, after all my owner didn't tell me *not* to do that...I'll just send that information right now.
**Your browser:** "La la, waiting for my user to do something..."
diff --git a/app/pages/6.0/02.background/03.develop-locally-serve-globally/docs.md b/app/pages/6.0/02.background/03.develop-locally-serve-globally/docs.md
index 7982255f..35a51eed 100644
--- a/app/pages/6.0/02.background/03.develop-locally-serve-globally/docs.md
+++ b/app/pages/6.0/02.background/03.develop-locally-serve-globally/docs.md
@@ -1,7 +1,6 @@
---
title: Develop Locally, Serve Globally
description: The right way to approach development.
-obsolete: true
---
Just about every week, we see someone wander into [chat](https://chat.userfrosting.com) and ask:
@@ -28,9 +27,9 @@ In this same vein, any framework or CMS that has you do a "one-click install" is
## Setting up a local development environment
-If you think that setting up a local environment is too much work, think again! On a MacOS or Linux computer, setting up a local environment simply consist of installing a couple of apps through the command line. On a Windows 10 or 11 machine, an additional step is required : Installing the *Windows Subsystem for Linux (WSL2)*!
+If you think that setting up a local environment is too much work, think again! On a MacOS or Linux computer, setting up a local environment simply consist of installing a couple of apps through the command line. On a Windows 10 or 11 machine, an additional step is required : Installing the *Windows Subsystem for Linux (WSL2)*!
-And the sprinkle on the cupcake is the [next chapter](installation) will teach you how to do everything yourself!
+And the sprinkle on the cupcake is the [Installation chapter](/installation) will teach you how to do everything yourself!
> [!WARNING]
> There are a number of "one-click" installers available, which can set up your machine with a complete web application stack in just a few minutes: **XAMPP**, **MAMP**, **WampServer**, etc. **These are not officially supported by UserFrosting and we do not recommend using them.** They can be slow, out of date or use obscure configuration. They were useful at some point, but with modern tools, especially with WSL2 on Windows, it's never been easier to install every tool you need locally If you insist on using a "one-click" solution, [Docker](#alternatives-to-installing-a-stack-natively-docker) is a [great, modern alternative](https://www.reddit.com/r/PHP/comments/gqhg15/comment/frt8cp0/).
diff --git a/app/pages/6.0/02.background/04.dont-reinvent-the-wheel/docs.md b/app/pages/6.0/02.background/04.dont-reinvent-the-wheel/docs.md
index 484cc5dd..1b22de1e 100644
--- a/app/pages/6.0/02.background/04.dont-reinvent-the-wheel/docs.md
+++ b/app/pages/6.0/02.background/04.dont-reinvent-the-wheel/docs.md
@@ -1,7 +1,6 @@
---
title: Don't Reinvent the Wheel
description: Using third-party components reduces the amount of software maintenance you have to do and documentation you have to write, and lets you draw on the wider community of other developers who use those packages for troubleshooting and support.
-obsolete: true
---
I think that for a lot of developers - novices and professionals alike - building on top of others' work can seem like a betrayal of our trade. We're not "real" developers unless we built everything with our bare hands from scratch, and know firsthand the nitty-gritty details of how our code works. With third-party components, we have to take time to actually *learn* how to use them, and follow *their* rules. I get it. It all feels so antithetical to the DIY spirit that got so many of us into coding in the first place. Trust me, as someone who built a cold frame out of some doors and framing I found in the dumpster, I know:
diff --git a/app/pages/6.0/02.background/05.security/01.server-misconfiguration/docs.md b/app/pages/6.0/02.background/05.security/01.server-misconfiguration/docs.md
index ec95be1d..9a6de812 100644
--- a/app/pages/6.0/02.background/05.security/01.server-misconfiguration/docs.md
+++ b/app/pages/6.0/02.background/05.security/01.server-misconfiguration/docs.md
@@ -1,7 +1,6 @@
---
title: Server Misconfiguration
description: Server misconfiguration is one of the top 10 vulnerabilities of any web application, according to OWASP. Most of these misconfigurations occur because of inexperienced developers or system administrators, and are simple to fix.
-obsolete: true
---
As we discussed in [The Client-Server Conversation](background/the-client-server-conversation), it's important to distinguish between the code that is running on the server, and the response that it sends back to the client.
diff --git a/app/pages/6.0/02.background/05.security/02.csrf-protection/docs.md b/app/pages/6.0/02.background/05.security/02.csrf-protection/docs.md
new file mode 100644
index 00000000..b0af6fd8
--- /dev/null
+++ b/app/pages/6.0/02.background/05.security/02.csrf-protection/docs.md
@@ -0,0 +1,48 @@
+---
+title: CSRF Protection
+description: Cross-Site Request Forgery (CSRF) attacks trick authenticated users into executing unwanted actions. UserFrosting provides built-in CSRF protection to prevent these attacks.
+---
+
+## What is CSRF?
+
+Cross-Site Request Forgery (CSRF) is an attack that forces an authenticated user to execute unwanted actions on a web application. If a user is authenticated to your site, an attacker can trick their browser into making requests to your application on their behalf.
+
+For example, imagine a user is logged into your banking application. They then visit a malicious website that contains this hidden form:
+
+```html
+
+
+```
+
+Because the user is already authenticated (their browser has the session cookie), this request would succeed unless you have CSRF protection in place.
+
+## How UserFrosting Protects Against CSRF
+
+UserFrosting uses **CSRF tokens** to protect against these attacks. A CSRF token is a unique, secret value generated by the server and tied to the user's session. When a form is submitted or an AJAX request is made, the token must be included in the request. The server validates that the token matches what it expects for that session.
+
+We'll see in [a later chapter](/routes-and-controllers/client-input/csrf-guard) how to include CSRF tokens in your forms and AJAX requests.
+
+## When CSRF Protection Applies ?
+
+CSRF protection is relevant to:
+- **POST** requests
+- **PUT** requests
+- **DELETE** requests
+- **PATCH** requests
+
+**GET** requests are not protected, as they shouldn't modify state anyway.
+
+If a CSRF token is missing or invalid, the system should reject the request with a 400 Bad Request error. This protects your application but means you need to ensure:
+
+1. Every form includes the CSRF token
+2. AJAX requests include the token in the payload
+3. Tokens are refreshed if sessions expire
+4. Single-page applications properly manage token lifecycle
+
+> [!WARNING]
+> Never disable CSRF protection in production. If you're having issues with CSRF validation, fix the root cause rather than disabling this critical security feature.
+
+For stateless APIs that use token-based authentication (like OAuth or JWT), CSRF protection is typically not needed because there are no cookies involved. However, if your API endpoints rely on session cookies for authentication, CSRF protection is essential.
diff --git a/app/pages/6.0/02.background/05.security/03.xss-prevention/docs.md b/app/pages/6.0/02.background/05.security/03.xss-prevention/docs.md
new file mode 100644
index 00000000..a7655b97
--- /dev/null
+++ b/app/pages/6.0/02.background/05.security/03.xss-prevention/docs.md
@@ -0,0 +1,191 @@
+---
+title: XSS Prevention
+description: Cross-Site Scripting (XSS) allows attackers to inject malicious scripts into your web pages. Learn how UserFrosting's templating engine and best practices protect against XSS attacks.
+---
+
+## What is XSS?
+
+Cross-Site Scripting (XSS) is a vulnerability that allows attackers to inject malicious JavaScript into web pages viewed by other users. When successful, XSS can allow attackers to:
+
+- Steal session cookies and impersonate users
+- Capture keystrokes and form data
+- Deface websites
+- Redirect users to malicious sites
+- Execute actions on behalf of the victim
+
+There are three main types of XSS:
+
+### 1. Stored XSS (Persistent)
+
+The malicious script is permanently stored on your server (e.g., in a database) and served to users when they view the affected page.
+
+**Example:** An attacker posts a comment containing:
+```html
+
+```
+
+If this is stored and displayed without proper escaping, it will execute in every visitor's browser.
+
+### 2. Reflected XSS
+
+The malicious script is reflected off the web server in an error message, search result, or any response that includes user input.
+
+**Example:** A search page that displays:
+```html
+You searched for:
+```
+
+### 3. DOM-based XSS
+
+The vulnerability exists in client-side code that improperly handles user input.
+
+**Example:**
+```javascript
+// Dangerous!
+document.getElementById('welcome').innerHTML =
+ 'Welcome, ' + location.hash.substring(1);
+```
+
+An attacker could use a URL like `#` to inject code.
+
+## How UserFrosting Protects Against XSS
+
+### Automatic Output Escaping with Twig
+
+UserFrosting uses **Twig** as its templating engine, which **automatically escapes all output by default**. This means that HTML special characters are converted to their safe equivalents:
+
+```twig
+{# This is safe - Twig automatically escapes the output #}
+
Welcome, {{ user.name }}
+```
+
+If `user.name` is ``, Twig will output:
+```html
+
Welcome, <script>alert('XSS')</script>
+```
+
+The script tag is displayed as text instead of being executed.
+
+### Rendering Raw HTML (Use with Caution)
+
+Sometimes you genuinely need to output HTML (e.g., user-generated content with formatting). Twig provides the `raw` filter for this, but **use it with extreme caution**:
+
+```twig
+{# Only use raw with trusted, sanitized content! #}
+{{ article.content | raw }}
+```
+
+> [!CAUTION]
+> Never use the `raw` filter with unsanitized user input. Always sanitize HTML content using a library like [HTML Purifier](http://htmlpurifier.org/) before storing or displaying it.
+
+### Sanitizing HTML Input
+
+If you need to allow users to submit HTML (e.g., rich text editors), you must sanitize it server-side before storing it:
+
+```php
+use HTMLPurifier;
+use HTMLPurifier_Config;
+
+// Create a sanitizer configuration
+$config = HTMLPurifier_Config::createDefault();
+$config->set('HTML.Allowed', 'p,b,i,em,strong,a[href],ul,ol,li');
+
+$purifier = new HTMLPurifier($config);
+$cleanHtml = $purifier->purify($userInput);
+```
+
+This removes any dangerous tags and attributes while preserving safe formatting.
+
+## JavaScript Best Practices
+
+### Avoid innerHTML with User Data
+
+Never use `innerHTML` or similar methods with unsanitized user input:
+
+```javascript
+// Dangerous!
+element.innerHTML = userInput;
+
+// Safe alternatives:
+element.textContent = userInput; // For plain text
+element.innerText = userInput; // For plain text
+```
+
+### Use textContent or createElement
+
+For dynamic content, use safe DOM methods:
+
+```javascript
+// Safe approach
+const div = document.createElement('div');
+div.textContent = userInput; // Automatically escaped
+parent.appendChild(div);
+```
+
+### Be Careful with eval() and Similar Functions
+
+Never use `eval()`, `Function()`, `setTimeout(string)`, or `setInterval(string)` with user input:
+
+```javascript
+// Extremely dangerous!
+eval(userInput);
+setTimeout(userInput, 1000);
+
+// Safe alternatives use functions directly:
+setTimeout(() => {
+ // Your code here
+}, 1000);
+```
+
+### Validate URLs in Anchors
+
+When creating links dynamically, validate that URLs don't use the `javascript:` protocol:
+
+```javascript
+// Dangerous if url comes from user input
+element.href = url;
+
+// Safer approach
+function isSafeUrl(url) {
+ return url.startsWith('http://') ||
+ url.startsWith('https://') ||
+ url.startsWith('/');
+}
+
+if (isSafeUrl(url)) {
+ element.href = url;
+}
+```
+
+## Content Security Policy (CSP)
+
+Consider implementing a Content Security Policy header to provide an additional layer of protection. CSP restricts what resources can be loaded and executed:
+
+```
+Content-Security-Policy:
+ default-src 'self';
+ script-src 'self' https://trusted-cdn.com;
+ style-src 'self' 'unsafe-inline';
+ img-src 'self' data: https:;
+```
+
+This tells the browser to only execute scripts from your domain and a trusted CDN, significantly reducing the impact of XSS vulnerabilities.
+
+## Vue.js and XSS
+
+If you're using Vue components in UserFrosting:
+
+```html
+
+
+
{{ userInput }}
+
+
+
+
+```
+
+> [!WARNING]
+> Never use `v-html` with unsanitized user input. The same sanitization rules apply as with Twig's `raw` filter.
diff --git a/app/pages/6.0/02.background/05.security/04.sql-injection/docs.md b/app/pages/6.0/02.background/05.security/04.sql-injection/docs.md
new file mode 100644
index 00000000..73656a86
--- /dev/null
+++ b/app/pages/6.0/02.background/05.security/04.sql-injection/docs.md
@@ -0,0 +1,272 @@
+---
+title: SQL Injection Prevention
+description: SQL injection is one of the most dangerous web vulnerabilities. Learn how UserFrosting's use of Eloquent ORM and prepared statements protects your application.
+---
+
+## What is SQL Injection?
+
+SQL injection is a code injection technique where attackers insert malicious SQL code into queries, potentially allowing them to:
+
+- Read sensitive data from the database
+- Modify or delete database records
+- Execute administrative operations
+- In some cases, execute commands on the operating system
+
+### Example of a Vulnerable Query
+
+Consider this dangerous PHP code:
+
+```php
+// NEVER DO THIS!
+$username = $_POST['username'];
+$password = $_POST['password'];
+
+$sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
+$result = $db->query($sql);
+```
+
+An attacker could submit:
+- Username: `admin' --`
+- Password: (anything)
+
+The resulting query becomes:
+```sql
+SELECT * FROM users WHERE username = 'admin' -- AND password = ''
+```
+
+The `--` comments out the rest of the query, allowing the attacker to log in as admin without knowing the password!
+
+## How UserFrosting Protects Against SQL Injection
+
+UserFrosting uses **Laravel's Eloquent ORM** and the **query builder**, which automatically use **prepared statements** with parameter binding. This separates SQL code from data, making injection attacks impossible.
+
+### Safe Queries with Eloquent
+
+When using Eloquent models, queries are automatically protected:
+
+```php
+use UserFrosting\Sprinkle\Account\Database\Models\User;
+
+// Safe - uses parameter binding internally
+$user = User::where('username', $username)
+ ->where('email', $email)
+ ->first();
+
+// Also safe - even with multiple conditions
+$users = User::where('last_login', '>', $date)
+ ->where('group_id', $groupId)
+ ->get();
+```
+
+### Safe Queries with the Query Builder
+
+The query builder also uses prepared statements:
+
+```php
+use Illuminate\Database\Connection;
+
+class MyService
+{
+ public function __construct(
+ protected Connection $db
+ ) {}
+
+ public function getActiveUsers(): Collection
+ {
+ // Safe - parameters are bound
+ return $this->db->table('users')
+ ->where('status', 'active')
+ ->where('age', '>', 18)
+ ->get();
+ }
+
+ public function getAdmins(): Collection
+ {
+ // Safe - using whereIn
+ return $this->db->table('users')
+ ->whereIn('role_id', [1, 2, 3])
+ ->get();
+ }
+}
+```
+
+### Parameterized Raw Queries
+
+If you need to use raw SQL, always use parameter binding:
+
+```php
+// Safe - using parameter binding with positional parameters
+$users = $this->db->select(
+ 'SELECT * FROM users WHERE email = ? AND status = ?',
+ [$email, 'active']
+);
+
+// Also safe - using named parameters
+$users = $this->db->select(
+ 'SELECT * FROM users WHERE email = :email AND status = :status',
+ ['email' => $email, 'status' => 'active']
+);
+```
+
+## Dangerous Patterns to Avoid
+
+### Never Concatenate User Input into Queries
+
+```php
+// EXTREMELY DANGEROUS!
+$sql = "SELECT * FROM users WHERE username = '" . $username . "'";
+$this->db->select($sql);
+
+// Also dangerous with query builder!
+$this->db->table('users')
+ ->whereRaw("username = '" . $username . "'") // DON'T DO THIS
+ ->get();
+```
+
+### Be Careful with WhereRaw and Raw Expressions
+
+While `whereRaw()` can be necessary for complex queries, never include unescaped user input:
+
+```php
+// Dangerous!
+$this->db->table('users')
+ ->whereRaw("YEAR(created_at) = " . $_GET['year'])
+ ->get();
+
+// Safe - use parameter binding
+$this->db->table('users')
+ ->whereRaw("YEAR(created_at) = ?", [$year])
+ ->get();
+```
+
+### Dynamic Column Names
+
+Be extremely careful when user input determines which column to query:
+
+```php
+// Dangerous if $column comes from user input!
+$results = User::where($column, $value)->get();
+
+// Safe - whitelist allowed columns
+$allowedColumns = ['username', 'email', 'created_at'];
+if (in_array($column, $allowedColumns)) {
+ $results = User::where($column, $value)->get();
+} else {
+ throw new \InvalidArgumentException('Invalid column name');
+}
+```
+
+### ORDER BY and Dynamic Sorting
+
+User-controlled sorting can be exploited:
+
+```php
+// Dangerous!
+$orderBy = $_GET['sort']; // Could be: "username; DELETE FROM users--"
+User::orderBy($orderBy)->get();
+
+// Safe - whitelist columns and directions
+$allowedColumns = ['username', 'email', 'created_at'];
+$allowedDirections = ['asc', 'desc'];
+
+$column = in_array($_GET['sort'], $allowedColumns) ? $_GET['sort'] : 'username';
+$direction = in_array($_GET['dir'], $allowedDirections) ? $_GET['dir'] : 'asc';
+
+User::orderBy($column, $direction)->get();
+```
+
+## Advanced Protection Techniques
+
+### Input Validation with Fortress
+
+While Eloquent protects against SQL injection, you should still validate input. UserFrosting includes **Fortress**, a validation and sanitization library that helps ensure data conforms to expected types and formats:
+
+```php
+use UserFrosting\Fortress\RequestSchema;
+use UserFrosting\Fortress\Adapter\JqueryValidationAdapter;
+
+// Define validation rules
+$schema = new RequestSchema('schema://requests/user.yaml');
+
+// Validate request data
+$validator = new JqueryValidationAdapter($schema, $this->translator);
+$validator->validate($data);
+
+// At this point, data is validated and safe to use
+$user = User::find($data['user_id']);
+```
+
+Fortress schemas can define:
+- Data types (integer, string, email, etc.)
+- Length constraints
+- Regular expression patterns
+- Custom validation rules
+
+This provides defense-in-depth: even if an attacker somehow bypassed client-side validation, Fortress catches invalid input on the server before it reaches your database queries.
+
+For simple type validation:
+
+```php
+// Validate that user ID is actually a number
+if (!is_numeric($userId)) {
+ throw new \InvalidArgumentException('User ID must be numeric');
+}
+
+$user = User::find($userId);
+```
+
+### Principle of Least Privilege
+
+Configure your database user with minimal permissions:
+
+```sql
+-- Database user should only have necessary permissions
+GRANT SELECT, INSERT, UPDATE ON myapp.* TO 'myapp_user'@'localhost';
+
+-- Avoid giving DELETE or DROP permissions unless necessary
+```
+
+### Use Views for Complex Queries
+
+For complex reporting queries, consider using database views:
+
+```sql
+CREATE VIEW active_users AS
+SELECT id, username, email, created_at
+FROM users
+WHERE status = 'active' AND deleted_at IS NULL;
+```
+
+Then query the view safely:
+```php
+$users = $this->db->table('active_users')->get();
+```
+
+## What About Table/Column Names?
+
+Eloquent and the query builder handle table and column names correctly, but if you need to work with dynamic table names:
+
+```php
+// Whitelist table names
+$allowedTables = ['users', 'roles', 'permissions'];
+
+if (!in_array($tableName, $allowedTables)) {
+ throw new \InvalidArgumentException('Invalid table name');
+}
+
+$this->db->table($tableName)->get();
+```
+
+## Testing for SQL Injection
+
+You can test your application for SQL injection vulnerabilities by trying these payloads in your inputs:
+
+```
+' OR '1'='1
+' OR '1'='1' --
+' OR '1'='1' /*
+admin' --
+') OR ('1'='1
+```
+
+If your application uses Eloquent/query builder properly, these should be treated as literal strings and won't execute as SQL.
diff --git a/app/pages/6.0/02.background/05.security/05.authentication-and-passwords/docs.md b/app/pages/6.0/02.background/05.security/05.authentication-and-passwords/docs.md
new file mode 100644
index 00000000..9ae17fca
--- /dev/null
+++ b/app/pages/6.0/02.background/05.security/05.authentication-and-passwords/docs.md
@@ -0,0 +1,78 @@
+---
+title: Authentication & Password Security
+description: Proper authentication and password handling are fundamental to application security. Learn how UserFrosting implements secure authentication and password storage.
+---
+
+## Password Storage
+
+Never, ever store passwords in plain text. If your database is compromised, plain text passwords allow attackers to access user accounts not only on your site, but potentially on other sites where users have reused passwords.
+
+## How UserFrosting Stores Passwords and Secures Accounts
+
+UserFrosting uses **bcrypt** hashing to securely store passwords. Bcrypt is specifically designed for password hashing and includes:
+
+- **Salting**: Each password gets a unique salt
+- **Cost factor**: Computational difficulty that can be increased over time
+- **Slow hashing**: Deliberately slow to make brute-force attacks impractical
+
+### Minimum Password Complexity
+
+UserFrosting's default password rules require:
+- Minimum length (typically 8-12 characters)
+- Mix of character types (uppercase, lowercase, numbers, symbols)
+
+These can be customized through validation rules and configuration.
+
+### Session Hijacking Prevention
+
+UserFrosting implements several protections against session hijacking:
+
+**1. Regenerate Session ID on Login**
+
+This prevents session fixation attacks where an attacker tricks a user into using a known session ID.
+
+**2. HTTP-Only Cookies**
+
+Session cookies are marked `HttpOnly`, preventing JavaScript from accessing them. This mitigates XSS-based session theft.
+
+**3. Secure Flag for HTTPS**
+
+When using HTTPS, session cookies are marked `Secure`, ensuring they are only sent over encrypted connections.
+
+**4. SameSite Cookie Attribute**
+
+Helps prevent CSRF attacks by restricting cross-site cookie sending.
+
+### Session Timeout
+
+Implement appropriate session timeouts to limit the window for session hijacking:
+
+```php
+// UserFrosting's config
+'session' => [
+ 'timeout' => 3600, // 1 hour in seconds
+ 'remember_me_timeout' => 604800, // 1 week for "remember me"
+],
+```
+
+### Multi-Factor Authentication (MFA)
+
+While UserFrosting does not include built-in multi-factor authentication (MFA) by default, it is highly recommended for enhancing security.
+
+Consider implementing multi-factor authentication for sensitive applications. UserFrosting can be extended with MFA using packages like:
+
+- TOTP (Time-based One-Time Password) - Google Authenticator, Authy
+- SMS-based codes
+- Email-based codes
+- Hardware tokens (U2F, WebAuthn)
+
+> [!NOTE]
+> Future versions of UserFrosting may include built-in MFA support. Check the documentation for updates.
+
+### Throttling
+
+Protect against brute-force attacks by implementing account lockout. After a certain number of failed login attempts, UserFrosting temporarily locks the login form for that user or IP address.
+
+### Password Reset Security
+
+Password reset functionality is a common attack vector. UserFrosting implements secure password resets using time-limited, single-use tokens and email verification.
diff --git a/app/pages/6.0/02.background/05.security/docs.md b/app/pages/6.0/02.background/05.security/docs.md
index 99e5e09f..76957790 100644
--- a/app/pages/6.0/02.background/05.security/docs.md
+++ b/app/pages/6.0/02.background/05.security/docs.md
@@ -1,9 +1,44 @@
---
title: Security
-description: It is essential to understand some basic security concepts before diving into web development. With an understanding of how the most common vulnerabilities work and some diligence in configuring your system, UserFrosting sets you up with an application that is robust against most common attack vectors.
-obsolete: true
+description: It is essential to understand some basic security concepts before diving into web development. With an understanding of how the most common vulnerabilities work and some diligence in configuring your system, UserFrosting sets you up with an application that is robust against most common attack vectors.
---
It is essential to understand some basic security concepts before diving into web development.
-If you aren't familiar with [OWASP](https://www.owasp.org/index.php/Main_Page), they are considered the authoritative source on web application security. Most of what we discuss in this section is covered in OWASP's [Top 10](https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project) list; nonetheless we paraphrase it here and discuss the strategies and features that UserFrosting offers to mitigate many of these vulnerabilities.
+If you aren't familiar with [OWASP](https://owasp.org/), they are considered the authoritative source on web application security. Most of what we discuss in this section is covered in OWASP's [Top 10](https://owasp.org/www-project-top-ten/) list; nonetheless we paraphrase it here and discuss the strategies and features that UserFrosting offers to mitigate many of these vulnerabilities.
+
+This section covers the most critical security vulnerabilities you need to understand:
+
+- [Server Misconfiguration](/background/security/server-misconfiguration): Learn why sensitive error messages should never appear in production and how to properly handle logging.
+- [CSRF Protection](/background/security/csrf-protection) : Understand Cross-Site Request Forgery attacks and how UserFrosting's built-in CSRF tokens protect your forms and AJAX requests.
+- [XSS Prevention](/background/security/xss-prevention) : Learn how Cross-Site Scripting works and how Twig's automatic output escaping protects your application from malicious script injection.
+- [SQL Injection Prevention](/background/security/sql-injection) : Discover why SQL injection is so dangerous and how Eloquent ORM and prepared statements keep your database secure.
+- [Authentication & Password Security](/background/security/authentication-and-passwords) : Master the essentials of secure password storage, session management, password resets, and multi-factor authentication.
+
+## Security is a Mindset
+
+Security isn't just about implementing specific features—it's about developing the right mindset throughout the development process:
+
+**Principle of Least Privilege:** Grant only the minimum permissions necessary. Database users, file permissions, API keys—everything should have just enough access to do its job and no more.
+
+**Defense in Depth:** Never rely on a single security measure. Layer multiple protections so that if one fails, others are still in place.
+
+**Fail Securely:** When something goes wrong, make sure your application fails in a secure way. Don't display stack traces in production, don't fall back to permissive access, and don't leak sensitive information in error messages.
+
+**Input Validation:** Never trust user input. Validate on the server side (client-side validation is only for user experience). Whitelist what's allowed rather than blacklisting what's not.
+
+**Keep Dependencies Updated:** Regularly update UserFrosting and all third-party packages. Security vulnerabilities are discovered constantly, and updates often include critical security patches.
+
+> [!IMPORTANT]
+> Security is not a one-time task—it's an ongoing process. Stay informed about new vulnerabilities, follow security best practices, and regularly audit your application.
+
+## Additional Resources
+
+- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
+- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
+- [PHP Security Guide](https://www.php.net/manual/en/security.php)
+- [Laravel Security Best Practices](https://laravel.com/docs/security) (many concepts apply to UserFrosting)
+- [CWE Top 25 Most Dangerous Software Weaknesses](https://cwe.mitre.org/top25/)
+
+> [!NOTE]
+> Remember: UserFrosting provides many security features out of the box, but it's ultimately your responsibility to use them correctly and follow security best practices in your custom code.
diff --git a/app/pages/6.0/02.background/06.seo/docs.md b/app/pages/6.0/02.background/06.seo/docs.md
index e708717c..9dd4c392 100644
--- a/app/pages/6.0/02.background/06.seo/docs.md
+++ b/app/pages/6.0/02.background/06.seo/docs.md
@@ -1,7 +1,6 @@
---
title: Search Engine Optimization
description: Search Engine Optimization (SEO) is an integral part of the design and development process. We discuss the major important factors in getting a page to rank well, and how they fit in with UserFrosting's features and overall architecture.
-obsolete: true
---
Search Engine Optimization (SEO) is an integral part of the design and development process. Getting the public side of your website to rank well in search results should be something you consider from the very beginning, and not an afterthought once you're getting ready to deploy.
@@ -63,17 +62,18 @@ Make your image files exactly the size at which you intend to display them (in s
### Use compiled assets in production
-The way UserFrosting [serves assets](asset-management) is great for development and debugging, but not so much for production. The asset files themselves (Javascript and CSS) are larger than they need to be to perform their function.
+The way UserFrosting [serves assets](asset-management) is great for development and debugging, but not so much for production. The asset files themselves (JavaScript and CSS) are larger than they need to be to perform their function.
-Using Webpack solves all of these problems:
+Using **Vite** (UserFrosting's build tool) solves all of these problems:
-1. It **minifies** your Javascript and CSS files, making files smaller by making variable names shorter and removing comments and whitespace;
-2. It **concatenates** Javascript and CSS files, reducing the number of requests needed by the page;
-3. It copies the assets to the `public/` directory, so that they can be served directly by your webserver instead of going through the application lifecycle.
+1. It **minifies** your JavaScript and CSS files, making files smaller by removing comments, whitespace, and optimizing code;
+2. It **bundles** JavaScript and CSS files efficiently using code splitting and tree shaking, reducing the amount of code loaded;
+3. It optimizes assets for production with automatic hashing for cache busting;
+4. It generates optimized builds that are served directly by your webserver for maximum performance.
### Caching
-[Caching](advanced/caching/intro) should happen on a number of levels throughout your application, on both the server and client sides. On the server, UserFrosting automatically caches route resolutions and fully-rendered Twig templates when you use the `production` configuration profile. You can also configure the webserver itself to cache entire responses. For example, see [nginx's caching documentation](https://www.nginx.com/resources/admin-guide/content-caching/).
+[Caching](advanced/caching) should happen on a number of levels throughout your application, on both the server and client sides. On the server, UserFrosting automatically caches route resolutions and fully-rendered Twig templates when you use the `production` environment mode. You can also configure the webserver itself to cache entire responses. For example, see [nginx's caching documentation](https://www.nginx.com/resources/admin-guide/content-caching/).
Caching can also happen in the client's browser. For example, you don't want the client to have to retrieve images and Javascript files each time they visit your page, if those assets haven't changed since their last visit. Browser caching is handled by the `Cache-Control` response header, which is the server's way of telling the client's browser how long they should cache the response of a particular request.
@@ -81,7 +81,7 @@ Setting the value of the `Cache-Control` header is also typically handled direct
The main issue with client-side caching is that you need some way of forcing the browser to refresh cached assets when they have changed (for example, when the site developers add a feature, fix a bug, or change some content). This is called **cache busting**, and the most common approach is to simply change the name of the asset so that browsers assume that it is a new resource to be loaded.
-Fortunately, UserFrosting's build tools take care of this as well. Each time you compile your assets, a random hash is used to name the compiled files. References to these assets in your pages are automatically updated to reflect these new names.
+Fortunately, Vite takes care of this automatically. Each time you build your assets for production, a content-based hash is used in the filename of compiled assets. References to these assets in your pages are automatically updated through Vite's manifest to reflect these new names.
> [!TIP]
> There are other steps that can be taken to further improve page performance, such as deferring the loading of Javascript and CSS, and inlining "above-the-fold" CSS. Google has recently released its [Pagespeed webserver module](https://developers.google.com/speed/pagespeed/module/) for Apache and nginx, which can automatically perform optimizations like these automatically and behind the scenes. We highly recommend that you look into installing and configuring this module if you use a supported webserver.
diff --git a/app/pages/6.0/02.background/chapter.md b/app/pages/6.0/02.background/chapter.md
index fa9c985a..00cc6782 100644
--- a/app/pages/6.0/02.background/chapter.md
+++ b/app/pages/6.0/02.background/chapter.md
@@ -7,6 +7,6 @@ description: "UserFrosting has a not-so-secret ulterior motive: to get you to be
# Web Dev, the Right Way
-UserFrosting has a not-so-secret ulterior motive: **to get you to become a better developer**.
+UserFrosting has a not-so-secret ulterior motive: **to get you to become a better developer**.
In this chapter, we'll talk about the state of modern web development tools and practices, and clear up some common misconceptions about how web applications actually work. Then, we'll go over key security concepts. Finally, we'll discuss best practices to make sure that users will be able to find and use your application when it's finished.
diff --git a/app/pages/6.0/03.structure/01.introduction/docs.md b/app/pages/6.0/03.structure/01.introduction/docs.md
index dfa4fc58..93972841 100644
--- a/app/pages/6.0/03.structure/01.introduction/docs.md
+++ b/app/pages/6.0/03.structure/01.introduction/docs.md
@@ -1,23 +1,22 @@
---
title: Your Application
description: A Sprinkle can contain assets, configuration files, translations, routes, PHP classes, and Twig templates.
-obsolete: true
---
-UserFrosting 4 introduced the **Sprinkle system** as a way to completely isolate the code and content that you and your team produce from the core UserFrosting installation. **UserFrosting 5** takes this concept a step further, requiring a new chapter on the basic UserFrosting project structure before even talking about downloading any code!
-
-It's important to understand how UserFrosting 5 is structured. This will be key to understand the installation process, the tools required to do so, and how all the parts fits together to create your own project.
+*UserFrosting 4* introduced the **Sprinkle system** as a way to completely isolate the code and content that you and your team produce from the core UserFrosting installation. *UserFrosting 5* took this concept a step further by separating all of UserFrosting's code from your own project code.
If you're familiar with UserFrosting 4, most of your code used to live along UserFrosting's code. Parts where separated in **Sprinkles**, but everything was located in your project folder. This meant if you were to host your project on Github, most of UserFrosting's code was also hosted in your repo. This was fine, but decreased modularity and made upgrades more difficult.
-To make things easier, UserFrosting 5 now separates all of your code from UserFrosting code. UserFrosting code is now handled by Composer. This is where the **App Skeleton** comes in.
+To make things easier, UserFrosting separates all of your code from UserFrosting code. UserFrosting code is handled by Composer. This is where the **App Skeleton** comes in.
+
+It's important to understand how UserFrosting is structured. This will be key to understand the installation process, the tools required to do so, and how all the parts fits together to create your own project.
## The App Skeleton, your project's template
-The **app skeleton** is a bare-bone UserFrosting project. Think of it like a starting kit, or template, to create your own application. Everything in the skeleton is meant to be modified. As such, the skeleton doesn't need to be a synced copy of the UserFrosting Github repository (called a ***fork***). It provides example pages and all the basic configuration to run a default UserFrosting application.
+The **app skeleton** is a bare-bone UserFrosting project. Think of it like a starting kit, or template, to create your own application. Everything in the skeleton is meant to be modified. As such, the skeleton doesn't need to be a synced copy of the UserFrosting Github repository (called a ***fork***). It provides example pages and all the basic configuration to run a default UserFrosting application.
> [!IMPORTANT]
-> While there is an official UserFrosting App Skeleton, it doesn't need to be the only one. Many skeletons could exist as starting points for new UserFrosting-based projects
+> While there is an official UserFrosting App Skeleton, it doesn't need to be the only one! Many skeletons could exist as starting points for new UserFrosting-based projects
But what makes a UserFrosting application, a UserFrosting application? What does it contain? Well, it's not much different than a normal modern PHP application. Your UserFrosting based project will consist of your code, plus a bunch of **dependencies**. These dependencies are all handled by Composer (which we'll explain later) and are themselves separated into three groups : **The Framework**, **External Libraries**, and **Sprinkles** :
diff --git a/app/pages/6.0/03.structure/02.dependencies/docs.md b/app/pages/6.0/03.structure/02.dependencies/docs.md
index 324f412a..9ae94c07 100644
--- a/app/pages/6.0/03.structure/02.dependencies/docs.md
+++ b/app/pages/6.0/03.structure/02.dependencies/docs.md
@@ -1,23 +1,22 @@
---
title: Built on the shoulders of giants
description: Detailed breakdown of a UserFrosting's dependencies.
-obsolete: true
---
-As detailed in a previous chapter, it's important [not to reinvent the wheel](background/dont-reinvent-the-wheel). That's why UserFrosting depends on a number of external libraries, called dependencies. Those are written by people and organizations external to UserFrosting, providing the base that UserFrosting works on. These dependencies are not tied to UserFrosting and can be used by anyone. Think of dependencies as the raw materials, like wood and concrete, you get from the hardware store to build a house. We simply "glued" them together to create awesomeness!
+As detailed in a previous chapter, it's important [not to reinvent the wheel](/background/dont-reinvent-the-wheel). That's why UserFrosting depends on a number of external libraries, called dependencies. Those are written by people and organizations external to UserFrosting, providing the base that UserFrosting works on. These dependencies are not tied to UserFrosting and can be used by anyone. Think of dependencies as the raw materials, like wood and concrete, you get from the hardware store to build a house. We simply "glued" them together to create awesomeness!
While UserFrosting uses dozens of dependencies, here's a rundown of the most important ones:
## Slim 4
-**[Slim](https://www.slimframework.com)** is a PHP _micro framework_ that helps you quickly write simple yet powerful web applications and APIs. Slim is the backbone of UserFrosting. To be more precise, **UserFrosting _is_ a Slim Application**!
+**[Slim](https://www.slimframework.com)** is a PHP _micro framework_ that helps you quickly write simple yet powerful web applications and APIs. Slim is the backbone of UserFrosting. To be more precise, **UserFrosting _is_ a Slim Application**!
Except for the Bakery system (which uses _[Symfony Console](#symfony-console-5)_), UserFrosting uses Slim at every level to perform middleware management, route collections, and everything else needed to actually display a web page.
## PHP-DI 7
**[PHP-DI](https://php-di.org)** is a _dependency injection container_. Dependency injection is one of the fundamental pillars of modern object-oriented software design. It is used extensively throughout UserFrosting to glue all services together while maintaining great flexibility to extend the basics functionalities of UserFrosting to create your own project. We'll explain dependency injection in detail in a later chapter. For now, it's only important to note **PHP-DI** is the dependency manager used by UserFrosting 5 to handle all dependency injection.
-## Eloquent 8
-**[Eloquent](https://laravel.com/docs/8.x/eloquent)** is part of the Laravel Framework. Eloquent makes it enjoyable to interact with a database. When using Eloquent, each database table has a corresponding "Model" that is used to interact with that table. In addition to retrieving records from the database table, Eloquent models allow you to insert, update, and delete records from the table as well.
+## Eloquent (Laravel 10)
+**[Eloquent](https://laravel.com/docs/10.x/eloquent)** is part of the Laravel Framework. Eloquent makes it enjoyable to interact with a database. When using Eloquent, each database table has a corresponding "Model" that is used to interact with that table. In addition to retrieving records from the database table, Eloquent models allow you to insert, update, and delete records from the table as well.
Eloquent is one of the most powerful and easy to use tools available to interact with a Database. That is why it's been chosen as the database handler since early versions of UserFrosting.
@@ -28,7 +27,19 @@ Eloquent is one of the most powerful and easy to use tools available to interact
**[Twig](https://twig.symfony.com/doc/)** is a flexible, fast, and secure template engine for PHP. Initially developed for the Symfony framework, Twig is easy to use. Twig provides the necessary tools to use the data generated by PHP and render the HTML page the end user gets to see.
## Symfony Console 5
-**[Symfony Console](https://symfony.com/doc/5.4/components/console.html)** eases the creation of beautiful and testable command line interfaces. This is used to power the **Bakery** command line interface tool used by UserFrosting.
+**[Symfony Console](https://symfony.com/doc/5.4/components/console.html)** eases the creation of beautiful and testable command line interfaces. This is used to power the **Bakery** command line interface tool used by UserFrosting.
-## Webpack Encore 4
-**[Webpack Encore](https://symfony.com/doc/current/frontend.html)** wraps Webpack, providing a clean & powerful API for bundling JavaScript modules, pre-processing CSS & JS and compiling and minifying assets. Encore is a professional asset system that's a delight to use. It is used by UserFrosting to serve all CSS and Javascript files, while enabling the use of other frameworks, like Vue.js, in the future.
+## Vite
+**[Vite](https://vitejs.dev)** is a modern build tool that provides lightning-fast development with Hot Module Replacement (HMR), instant server start, and optimized production builds. Vite is used by UserFrosting to build and serve all frontend assets including CSS, JavaScript, TypeScript, and Vue components.
+
+Vite offers native support for modern web technologies including TypeScript, Vue 3, CSS preprocessors, and ESM modules. It compiles and bundles frontend assets into optimized files that can be included in Twig templates, enabling the use of modern frameworks and tools in your application.
+
+## Vue 3
+**[Vue](https://vuejs.org)** is a progressive JavaScript framework for building user interfaces. Vue is used in UserFrosting to create reactive, interactive components on the frontend. Its component-based architecture makes it easy to build complex user interfaces from small, reusable pieces.
+
+Vue 3 brings improved performance, better TypeScript support, and a more flexible Composition API. UserFrosting uses Vue for dynamic features like the admin panel, user management interfaces, and interactive forms, providing a modern and responsive user experience.
+
+## UiKit
+**[UiKit](https://getuikit.com)** is a lightweight and modular front-end framework for developing fast and powerful web interfaces. UiKit provides the CSS framework and UI components used in UserFrosting's default theme (Pink Cupcake).
+
+UiKit offers a comprehensive collection of HTML, CSS, and JavaScript components that are simple to use and easy to customize. It provides the styling, layout, and interactive components that make UserFrosting's interface both beautiful and functional.
diff --git a/app/pages/6.0/03.structure/03.framework/docs.md b/app/pages/6.0/03.structure/03.framework/docs.md
index 09e0c948..c7839b00 100644
--- a/app/pages/6.0/03.structure/03.framework/docs.md
+++ b/app/pages/6.0/03.structure/03.framework/docs.md
@@ -1,7 +1,6 @@
---
title: The Framework
description: A simple description of the UserFrosting Framework.
-obsolete: true
---
The [**UserFrosting Framework**](https://github.com/userfrosting/framework/) contains the critical services required for UserFrosting to work. This is the only part of UserFrosting that is not considered a sprinkle. The reason for it not being considered a sprinkle is simple : the Framework contains the code required for the Sprinkle system to work. If it was a sprinkle itself, we'd be in a loop!
@@ -11,10 +10,10 @@ Aside from managing sprinkles (through the cleverly named _SprinkleManager_), th
## Shared Usage
The UserFrosting Framework also contains some parts that are not tied directly to UserFrosting. These parts could be used outside of UserFrosting, in a completely separate application.
-The documentation for each part is embedded in the next chapters, but you can still see each part's documentation on it's own :
- - [Cache](https://github.com/userfrosting/framework/tree/5.1/src/Cache) : Wrapper function for Laravel cache system for easier integration of the cache system in standalone projects.
- - [Config](https://github.com/userfrosting/framework/tree/5.1/src/Config) : Configuration files aggregator
- - [Fortress](https://github.com/userfrosting/framework/tree/5.1/src/Fortress) : A schema-driven system for elegant whitelisting, transformation and validation of user input, on both the client and server sides, from a unified set of rules.
- - [i81n](https://github.com/userfrosting/framework/tree/5.1/src/I18n) : The I18n module handles translation tasks.
- - [Session](https://github.com/userfrosting/framework/tree/5.1/src/Session) : PHP Session wrapper
- - [UniformResourceLocator](https://github.com/userfrosting/framework/tree/5.1/src/UniformResourceLocator) : The Uniform Resource Locator module handles resource aggregation and stream wrapper
+The documentation for each part is embedded in the next chapters, but you can still see each part's documentation on it's own :
+ - [Cache](https://github.com/userfrosting/framework/tree/main/src/Cache) : Wrapper function for Laravel cache system for easier integration of the cache system in standalone projects.
+ - [Config](https://github.com/userfrosting/framework/tree/main/src/Config) : Configuration files aggregator
+ - [Fortress](https://github.com/userfrosting/framework/tree/main/src/Fortress) : A schema-driven system for elegant whitelisting, transformation and validation of user input, on both the client and server sides, from a unified set of rules.
+ - [i81n](https://github.com/userfrosting/framework/tree/main/src/I18n) : The I18n module handles translation tasks.
+ - [Session](https://github.com/userfrosting/framework/tree/main/src/Session) : PHP Session wrapper
+ - [UniformResourceLocator](https://github.com/userfrosting/framework/tree/main/src/UniformResourceLocator) : The Uniform Resource Locator module handles resource aggregation and stream wrapper
diff --git a/app/pages/6.0/03.structure/04.sprinkles/docs.md b/app/pages/6.0/03.structure/04.sprinkles/docs.md
index 1990269e..28265861 100644
--- a/app/pages/6.0/03.structure/04.sprinkles/docs.md
+++ b/app/pages/6.0/03.structure/04.sprinkles/docs.md
@@ -1,7 +1,6 @@
---
title: Sprinkles, what are they?
description: Detailed breakdown of a sprinkle's contents.
-obsolete: true
---
Sprinkles are an integral part of UserFrosting. We'll see in detail how they work in [a later chapter](sprinkles), but for now it's important to have an overview.
@@ -14,7 +13,7 @@ Your app can have as many sprinkles as you want. A sprinkle could even depend on
## Bundled Sprinkles
-A default UserFrosting installation comes with **four** sprinkles, each of which will be downloaded by [Composer](installation/requirements/essential-tools-for-php#composer) in the `/vendor` directory during installation.
+A default UserFrosting installation comes with **four** sprinkles, each of which will be downloaded by [Composer](installation/requirements/essential-tools-for-php#composer) in the `/vendor` directory during installation.
Because UserFrosting is modular, you can decide to use these bundled sprinkles or not. You may or may not need the functionality each provides in your app. We'll go over how to enable and disable them [later](sprinkles/recipe#removing-default-sprinkles). For now, let's focus on their features.
@@ -32,9 +31,9 @@ The Account sprinkle depends on the Core sprinkle.
### Admin Sprinkle
The **Admin** sprinkle contains the routes and controllers to implement the administrative user management interface, as well as the group, role, and permission management interfaces.
-The Admin sprinkle depends on the Core, Account and AdminLTE sprinkles.
+The Admin sprinkle depends on the Core, Account and Pink Cupcake sprinkles.
-### AdminLTE Theme
-The **AdminLTE** theme sprinkle contains all the twig files and frontend assets to implement the [AdminLTE](https://adminlte.io) template.
+### Pink Cupcake Theme
+The **Pink Cupcake** theme sprinkle contains all the Twig templates and frontend assets built with [UiKit](https://getuikit.com). It provides a modern, responsive interface with Vue 3 components for interactive features.
-The AdminLTE sprinkle depends on the Core and Account sprinkles.
+The Pink Cupcake sprinkle depends on the Core and Account sprinkles.
diff --git a/app/pages/6.0/03.structure/chapter.md b/app/pages/6.0/03.structure/chapter.md
index 3b4e8c38..f3f77c0d 100644
--- a/app/pages/6.0/03.structure/chapter.md
+++ b/app/pages/6.0/03.structure/chapter.md
@@ -1,7 +1,6 @@
---
title: App Structure
description: UserFrosting is modular application framework built on the shoulders of giants. This chapter describes how UserFrosting is structured.
-obsolete: true
---
#### Chapter 3
diff --git a/app/pages/6.0/04.installation/01.requirements/01.basic-stack/docs.md b/app/pages/6.0/04.installation/01.requirements/01.basic-stack/docs.md
index 9f568e11..86622a8e 100644
--- a/app/pages/6.0/04.installation/01.requirements/01.basic-stack/docs.md
+++ b/app/pages/6.0/04.installation/01.requirements/01.basic-stack/docs.md
@@ -1,7 +1,6 @@
---
title: Basic Stack Requirements
description: UserFrosting requires a web server, PHP, and some sort of database.
-obsolete: true
---
The basic stack requirements for running UserFrosting are pretty typical of any web framework or CMS. Those requirements are the software required to _run_ UserFrosting, usually on a "server". These are different from the [developer tools used to build your website](installation/requirements/essential-tools-for-php) which we'll see on the next page.
@@ -20,7 +19,7 @@ To run UserFrosting, you'll need four things :
To run any website, you need *web server software*. Its tasks are to receive client requests, execute them, and send a reply. For a PHP website, the web server software won't execute the PHP code itself. Instead, it passes it to PHP which interprets the code and returns a response for the web server to display.
-The most popular web servers today are :
+The most popular web servers today are :
- [Nginx](https://www.nginx.com)
- [Apache](https://httpd.apache.org)
@@ -30,26 +29,26 @@ Any of these can be used to run UserFrosting. However, when developing locally (
## PHP Requirements
-You're probably here because you already know what PHP is. Great! The only thing left to say is UserFrosting requires **PHP 8.1** or higher. However, it's highly recommended you use the latest supported version, which is *PHP 8.3*.
+You're probably here because you already know what PHP is. Great! The only thing left to say is UserFrosting requires **PHP 8.1** or higher. However, it's highly recommended you use the latest supported version, which is *PHP 8.5*.
### But my host only supports PHP 7.x! Why do I need PHP 8.1?
Look, programming languages evolve, and PHP is no exception. Actually, PHP and other web languages have it particularly tough because they have so many responsibilities. PHP is the bouncer at the door: it has to be prepared to defend against constantly evolving security threats to your server. At the same time, it has to keep up with demand for faster performance, and satisfy the demand for new features from the [enormous](https://w3techs.com/technologies/overview/programming_language/all) PHP community.
-Honestly, PHP 8.1 isn't exactly cutting edge - in fact, **PHP 8.1 was no longer in active support as of [November 25th, 2023](http://php.net/supported-versions.php) and will be declared "End of Life" as of [November 25th, 2024](http://php.net/supported-versions.php)**.
+Honestly, PHP 8.1 isn't exactly cutting edge - in fact, **PHP 8.1 is already past its "End of Life" date ([November 25th, 2024](http://php.net/supported-versions.php)) and was no longer in active support as of [November 25th, 2023](http://php.net/supported-versions.php)**.
-In fact, we didn't make this decision directly. UserFrosting depends on a lot of third-party components, and *those* components require a minimum PHP version of _8.1_. As the whole community moves forward, UserFrosting does too. And fast! PHP 8.3 will only be supported until [November 23th, 2025](http://php.net/supported-versions.php) !
+In fact, we didn't make this decision directly. UserFrosting depends on a lot of third-party components, and *those* components require a minimum PHP version of _8.1_. As the whole community moves forward, UserFrosting does too. And fast! PHP 8.5 will only be supported until [November 2027](http://php.net/supported-versions.php) !
-If your hosting service doesn't have PHP 8.1 or above installed, call them and ask them to upgrade. If they refuse, point out that PHP 7.4 has been out of life for {{ date("now").diff(date("2022-11-28")).m }} months! To be honest, there is little reason to use a shared hosting (e.g. cPanel) service these days, especially when VPS providers like DigitalOcean and Amazon EC2 are so inexpensive. Unless you're stuck with shared hosting for some reason or another (fussy boss), [there's no real reason not to switch to a VPS](https://www.hostt.com/still-use-shared-hosting-theres-vps/).
+If your hosting service doesn't have PHP 8.1 or above installed, call them and ask them to upgrade. If they refuse, point out that PHP 7.4 has been out of life for more than **3 years**! To be honest, there is little reason to use a shared hosting (e.g. cPanel) service these days, especially when VPS providers like DigitalOcean and Amazon EC2 are so inexpensive. Unless you're stuck with shared hosting for some reason or another (fussy boss), [there's no real reason not to switch to a VPS](https://www.hostt.com/still-use-shared-hosting-theres-vps/).
As for your local development environment ([You _do_ have a local development environment, right ?](background/develop-locally-serve-globally)), if it's that much of a burden then... I don't know what to tell you. So what are you waiting for? Upgrade!
> [!NOTE]
-> As a reminder, as of UserFrosting 5.1, **PHP 8.3** is officially recommended. While you can still use UserFrosting 5 with PHP 8.1 and 8.2, upgrading to PHP 8.3 is highly recommended. Both PHP 8.1 and 8.2 support will eventually be removed the future.
+> As a reminder, **PHP 8.5** is officially recommended for UserFrosting 6. While you can still use UserFrosting with PHP 8.1, 8.2, 8.3 or 8.4, upgrading to PHP 8.5 is highly recommended for optimal performance and security.
### PHP Extensions
-UserFrosting and its dependencies requires some PHP Libraries and Extensions to be installed and enabled :
+UserFrosting and its dependencies requires some PHP Libraries and Extensions to be installed and enabled :
- [GD](https://www.php.net/manual/en/book.image.php)
- [DOM](https://www.php.net/manual/en/book.dom.php)
diff --git a/app/pages/6.0/04.installation/01.requirements/02.essential-tools-for-php/docs.md b/app/pages/6.0/04.installation/01.requirements/02.essential-tools-for-php/docs.md
index d42eada8..d7226d2b 100644
--- a/app/pages/6.0/04.installation/01.requirements/02.essential-tools-for-php/docs.md
+++ b/app/pages/6.0/04.installation/01.requirements/02.essential-tools-for-php/docs.md
@@ -1,7 +1,6 @@
---
title: Essential Tools for Modern PHP
description: A minimal set of tools that every PHP developer should have installed in their development environment.
-obsolete: true
---
On the previous page, we saw the softwares required to run UserFrosting. Now it's time to look at tools you'll need during development to build your UserFrosting application. These tools are not strictly required to be installed on your production server, which we'll cover in a [later chapter](going-live).
@@ -43,11 +42,11 @@ If you've been out of the PHP world for a while, you might have missed this crit
Composer also handles autoloading, which means that the days of needing long blocks of `include` or `require` statements in your code are over. It fully implements the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/) for autoloading, which further helps the PHP community develop a consistent approach to releasing and consuming packages.
> [!NOTE]
-> Following its release in October 2020, UserFrosting 5 now requires [**Composer 2**](https://getcomposer.org).
+> Following its release in October 2020, UserFrosting requires [**Composer 2**](https://getcomposer.org).
## Node.js
-**[Node.js](https://nodejs.org/en/)** is an an extremely popular JavaScript runtime built on Chrome's V8 JavaScript Engine. In recent years it has become extremely popular for creating multiplatform applications, and for its role in providing a means to run platform independent build tools like `gulp` and `grunt` (to name just a few). Node.js also includes `npm` (Node.js Package Manager).
+**[Node.js](https://nodejs.org/en/)** is an extremely popular JavaScript runtime built on Chrome's V8 JavaScript Engine. In recent years it has become extremely popular for creating multiplatform applications, and for its role in providing a means to run platform independent build tools like `gulp` and `grunt` (to name just a few). Node.js also includes `npm` (Node.js Package Manager).
Although UserFrosting does not _run_ on Node.js, it does use several Node-based tools to fetch client-side Javascript and CSS dependencies, as well as perform critical build tasks.
@@ -55,14 +54,14 @@ Although UserFrosting does not _run_ on Node.js, it does use several Node-based
> Even though we'll be using these tools to get our application ready for deployment, you don't need to install Node.js on your live server. You can install it locally, perform your installation and build tasks, and then push the built application to the live server afterwards.
> [!NOTE]
-> UserFrosting 5 requires **Node 18** or above.
+> UserFrosting requires **Node 18** or above.
## npm
-[npm](https://www.npmjs.com) stands for **N**ode **P**ackage **M**anager. npm is to Node.js what Composer is to PHP. It is used to grab the various Node packages that are required by UserFrosting's installation and build tools.
+[npm](https://www.npmjs.com) stands for **N**ode **P**ackage **M**anager. npm is to Node.js what Composer is to PHP. It is used to grab the various Node packages that are required by UserFrosting's installation and build tools.
> [!NOTE]
-> UserFrosting 5 requires **NPM 9** or above.
+> UserFrosting requires **NPM 9** or above.
## Code Editor
diff --git a/app/pages/6.0/04.installation/01.requirements/chapter.md b/app/pages/6.0/04.installation/01.requirements/chapter.md
index 04787e83..87ff9ed7 100644
--- a/app/pages/6.0/04.installation/01.requirements/chapter.md
+++ b/app/pages/6.0/04.installation/01.requirements/chapter.md
@@ -1,9 +1,8 @@
---
title: Requirements
description: UserFrosting has a few basic, sensible requirements - requirements that just about any modern web developer should already have set up!
-obsolete: true
---
# Requirements
-UserFrosting has a few basic requirements and makes use of some modern development tools - requirements and tools that just about any modern web developer should already have set up!
+UserFrosting has a few basic requirements and makes use of some modern development tools - requirements and tools that just about any modern web developer should already have set up!
diff --git a/app/pages/6.0/04.installation/02.environment/01.native/01.requirements/docs.md b/app/pages/6.0/04.installation/02.environment/01.native/01.requirements/docs.md
index 4a13499e..09e87377 100644
--- a/app/pages/6.0/04.installation/02.environment/01.native/01.requirements/docs.md
+++ b/app/pages/6.0/04.installation/02.environment/01.native/01.requirements/docs.md
@@ -1,9 +1,16 @@
---
title: Installing Requirements
description: Getting UserFrosting up and running in your development environment.
-obsolete: true
---
+Before you can start building with UserFrosting, you need the right tools installed. Think of these as your development toolkit—without them, you can't run UserFrosting or build your application. Some developers already have everything set up; others are starting fresh.
+
+This guide walks you through installing each required component: **PHP 8.1+**, **Composer** (PHP's package manager), **Node.js** and **npm** (for frontend assets), and a **command-line interface**. We intentionally skip web servers and databases here—UserFrosting can use PHP's built-in server and SQLite initially, so you can get started quickly and add those later if needed.
+
+By the end of this page, you'll have a complete development environment ready to run UserFrosting.
+
+## What You'll Install
+
If your local development environment doesn't already have the [required stack and tools](installation/requirements), we'll now set them up. We'll go through the following:
- [Command Line Interface](#cli)
@@ -22,33 +29,51 @@ If you followed the previous pages, you probably noticed two pieces of software
### CLI
-[plugin:content-inject](04.installation/_modular/cli)
+The [command line interface](installation/requirements/essential-tools-for-php#the-command-line-cli) will be required to perform most tasks in this guide. It's usage depends on your OS :
+
+#### MacOS
+If you're using MacOS, the **Terminal** is already installed on your computer. You'll find the app in `/System/Applications/Utilities/Terminal`.
+
+#### Linux
+Every Linux distro uses the command line. On Ubuntu for example, you can find a launcher for the terminal by clicking on the Activities item at the top left of the screen, then typing the first few letters of "terminal", "command", "prompt" or "shell".
+
+#### Windows
+The easiest way to setup a local dev environnement on Windows is through *Windows Subsystem for Linux* (WSL2). This is basically running Linux inside Windows. Best of both worlds! This also means most installation instructions for Windows you'll find on the internet won't work, as we're not technically *on* Windows, **we're on Ubuntu**. We'll instead use the Ubuntu installation instructions!
+
+See this guide for more detail on this process : [Set up a WSL development environment](https://learn.microsoft.com/en-us/windows/wsl/setup/environment). The gist of it is :
+
+1. Open *Windows Terminal*, which can be found in the [Microsoft Store](https://apps.microsoft.com/detail/9N0DX20HK701?hl=en-us&gl=US).
+2. Open the terminal and install WSL2 distro : `wsl --install`.
+3. During installation, enter a unix user with a password. Remember this password, you'll need it later!
+4. Restart Windows Terminal and open a new ***Ubuntu*** terminal. Each subsequent CLI usage on Windows will be from this Ubuntu terminal.
+
+When using Windows and WSL2, keep in mind your project files will be stored inside the Linux file system. For example, your project files will be in the Linux file system root directory (`\\wsl$\\home\\Project`), not the Windows file system root directory (`C:\Users\\Project or /mnt/c/Users//Project$`). See [Microsoft guide on file storage](https://learn.microsoft.com/en-us/windows/wsl/setup/environment#file-storage) for more information.
### PHP
-Installing PHP 8.3 locally will make it easier to develop locally, as it will allow you to run Composer locally, too.
+Installing PHP 8.5 locally will make it easier to develop locally, as it will allow you to run Composer locally, too.
#### MacOS
The easiest way to install PHP on MacOS is through Homebrew:
1. Install XCode Command Line Tools : `xcode-select --install`
2. Install [Homebrew](https://brew.sh) using their guide
-3. Install PHP 8.3, from the terminal : `brew install shivammathur/php/php@8.3`
+3. Install PHP 8.5, from the terminal : `brew install shivammathur/php/php@8.5`
> [!TIP]
> It's possible to use multiple versions of PHP on MacOS. See [shivammathur/php documentation](https://github.com/shivammathur/homebrew-php#switch-between-php-versions).
#### Linux & Windows WSL2
-Install PHP through the package manager. For example, on Ubuntu :
+Install PHP through the package manager. For example, on Ubuntu :
-1. Add [Ondřej Surý PPA](https://launchpad.net/~ondrej/+archive/ubuntu/php/) to get the latest version :
+1. Add [Ondřej Surý PPA](https://launchpad.net/~ondrej/+archive/ubuntu/php/) to get the latest version :
```bash
sudo add-apt-repository ppa:ondrej/php
sudo apt update
```
-2. Install PHP and the necessary extensions :
+2. Install PHP and the necessary extensions :
```bash
- sudo apt install php8.3 php8.3-gd php8.3-dom php8.3-zip php8.3-sqlite3 php8.3-pdo_mysql php8.3-curl php8.3-mbstring unzip
+ sudo apt install php8.5 php8.5-gd php8.5-dom php8.5-zip php8.5-sqlite3 php8.5-pdo_mysql php8.5-curl php8.5-mbstring unzip
```
#### Preflight checks
@@ -63,11 +88,11 @@ php -v
You should then see a message like:
```txt
-PHP 8.3.0 (cli) (built: Nov 21 2023 14:40:35) (NTS)
+PHP 8.5.0 (cli) (built: Dec 20 2024 12:30:45) (NTS)
Copyright (c) The PHP Group
-Zend Engine v4.3.0, Copyright (c) Zend Technologies
- with Xdebug v3.3.0, Copyright (c) 2002-2023, by Derick Rethans
- with Zend OPcache v8.3.0, Copyright (c), by Zend Technologies
+Zend Engine v4.5.0, Copyright (c) Zend Technologies
+ with Xdebug v3.4.0, Copyright (c) 2002-2024, by Derick Rethans
+ with Zend OPcache v8.5.0, Copyright (c), by Zend Technologies
```
This is the version of PHP which will be used by Composer. Make sure it meets the minimum required version for UserFrosting!
@@ -104,7 +129,7 @@ Composer version 2.5.4 2023-02-15 13:10:06
### Node
-It's now time to install [Node.js](https://nodejs.org/en/). Any version above **18.0** will work with UserFrosting 5, however we recommend using the latest LTS version of Node.js (18.18.2 LTS as of October 2023).
+It's now time to install [Node.js](https://nodejs.org/en/). Any version above **18.0** will work with UserFrosting, however we recommend using the latest LTS version of Node.js (20.x LTS as of 2025).
#### MacOS
@@ -157,7 +182,7 @@ While multiple solutions are available, two are recommended by UserFrosting : **
Mailpit can be installed on [MacOS through Homebrew](https://github.com/axllent/mailpit#install-via-package-managers), on Linux/WSL2 through their Bash Script](https://github.com/axllent/mailpit#install-via-bash-script-linux--mac), or through [Docker](https://mailpit.axllent.org/docs/install/docker/). By default, Mailpit UI can be access at [http://0.0.0.0:8025](http://0.0.0.0:8025).
-When using Mailpit with UserFrosting, the following parameters will need to be provided during UserFrosting installation, which we'll see on the next page :
+When using Mailpit with UserFrosting, the following parameters will need to be provided during UserFrosting installation, which we'll see on the next page :
| Param | Value |
|-------------|-----------|
@@ -168,11 +193,11 @@ When using Mailpit with UserFrosting, the following parameters will need to be p
#### Mailtrap
-[Mailtrap](https://mailtrap.io/) is similar to Mailpit, but it runs in the cloud, so there's nothing to install. However, Mailtrap is not open source. Mailtrap features a forever free plan that offers basic functionality for personal use. The *Free Sandbox* provides one inbox and up to 100 emails per month. It's a great way to get started, as it's super easy and fast to setup. For a more permanent solution however, Mailpit should be preferred.
+[Mailtrap](https://mailtrap.io/) is similar to Mailpit, but it runs in the cloud, so there's nothing to install. However, Mailtrap is not open source. Mailtrap features a forever free plan that offers basic functionality for personal use. The *Free Sandbox* provides one inbox and up to 100 emails per month. It's a great way to get started, as it's super easy and fast to setup. For a more permanent solution however, Mailpit should be preferred.
-To get started, simply create your account on [Mailtrap's website](https://mailtrap.io/register/signup).
+To get started, simply create your account on [Mailtrap's website](https://mailtrap.io/register/signup).
-When using Mailtrap with UserFrosting, the following parameters will need to be provided during UserFrosting installation, which we'll see on the next page :
+When using Mailtrap with UserFrosting, the following parameters will need to be provided during UserFrosting installation, which we'll see on the next page :
| Param | Value |
|---------------|--------------------------|
@@ -185,7 +210,7 @@ The *user* and *password* are unique to your Mailtrap inbox, and can be found in
## Optional Installation
-The next tools are not required in your local development environment to run UserFrosting. However, you may be interested in installing them anyway; or the instructions may be helpful for those tools which apply to you.
+The next tools are not required in your local development environment to run UserFrosting. However, you may be interested in installing them anyway; or the instructions may be helpful for those tools which apply to you.
### Git
diff --git a/app/pages/6.0/04.installation/02.environment/01.native/02.install/docs.md b/app/pages/6.0/04.installation/02.environment/01.native/02.install/docs.md
index 95940ddd..7138c232 100644
--- a/app/pages/6.0/04.installation/02.environment/01.native/02.install/docs.md
+++ b/app/pages/6.0/04.installation/02.environment/01.native/02.install/docs.md
@@ -1,7 +1,6 @@
---
title: Installing UserFrosting
description: Getting UserFrosting up and running in your development environment.
-obsolete: true
---
Now that your local development environment is setup and ready to go, it's finally time to download and access your first UserFrosting application for the first time !
@@ -10,8 +9,9 @@ Now that your local development environment is setup and ready to go, it's final
Use Composer to create an empty project with the latest version of UserFrosting skeleton into a new `UserFrosting` folder:
+
```bash
-composer create-project userfrosting/userfrosting UserFrosting "^5.1"
+composer create-project userfrosting/userfrosting UserFrosting "^6.0" --stability=beta
```
> [!TIP]
@@ -22,10 +22,7 @@ This may take some time to complete. If Composer has completed successfully, you
Next the **Bakery** will execute it's magic. You'll have to answer some questions, which will guide you into the configuration process. These will help you set up the **database** credentials, create the first **admin user** and install the third-party **assets**.
> [!NOTE]
-> If any error is encountered at this point, in the main project directory, run:
-> ```bash
-> $ php bakery bake
-> ```
+> If any error is encountered at this point, in the main project directory, run `php bakery bake` to re-attempt the installation.
You will first be prompted for your database credentials. This is the information PHP needs to connect to your database. If you followed our guide to setup, you can select **SQLite** and the default path when asked. If PHP can't connect to your database using these credentials, make sure you have entered the right information and re-run the `bake` command.
@@ -42,27 +39,38 @@ If the database connection is successful, the installer will then check that the
## Serve and visit your website
-At this point you can run locally using the PHP Built-in server :
+UserFrosting requires two servers to run locally: the PHP backend server and the Vite development server for frontend assets.
+
+**Terminal 1 - Start the PHP server:**
```bash
php bakery serve
```
+This starts the backend on [http://localhost:8080](http://localhost:8080).
+
+**Terminal 2 - Start the Vite dev server:**
+
+```bash
+npm run dev
+```
+
+This starts the Vite development server with Hot Module Replacement (HMR) for instant frontend updates.
+
+> [!TIP]
+> You can use the `==> Serve` VS Code task to start both servers simultaneously if you're using VS Code.
+
You can now access UserFrosting at [http://localhost:8080](http://localhost:8080). You should see the default UserFrosting pages and login with the newly created master account.

> [!TIP]
-> To stop the server, hit `ctrl+c`.
+> To stop either server, hit `ctrl+c` in its respective terminal.
-## Star the project and follow us on Twitter
+## Star the project
It will help us a lot if you could star [the UserFrosting project on GitHub](https://github.com/userfrosting/UserFrosting). Just look for the button in the upper right-hand corner!
[](https://github.com/userfrosting/UserFrosting)
-You should also follow us on Twitter for real-time news and updates:
-
-Follow @userfrosting
-
-Congratulations! Now that this is complete, you're ready to start developing your application by [creating your first Sprinkle](sprinkles).
+Congratulations! Now that this is complete, you're ready to start developing your application. Learn more about [Sprinkles](sprinkles) to understand how to structure and extend your UserFrosting application.
diff --git a/app/pages/6.0/04.installation/02.environment/01.native/docs.md b/app/pages/6.0/04.installation/02.environment/01.native/docs.md
index 76d5f0c2..31dd74c9 100644
--- a/app/pages/6.0/04.installation/02.environment/01.native/docs.md
+++ b/app/pages/6.0/04.installation/02.environment/01.native/docs.md
@@ -1,11 +1,10 @@
---
title: Native Installation
description: Getting UserFrosting up and running in your development environment.
-obsolete: true
---
-This **native installation** guide will first show you the steps to install all the tools and apps required to run your own local development environment. Once this is done, the second part contains the steps required to get UserFrosting itself up and running.
+This **native installation** guide will first show you the steps to install all the tools and apps required to run your own local development environment. Once this is done, the second part contains the steps required to get UserFrosting itself up and running.
-If you already have a local environment and you're familiar with tools like **Composer**, the first part also contains steps to make sure you have the *appropriate version* of everything set up. If you're already up to date, you can skip to the second part right away.
+If you already have a local environment and you're familiar with tools like **Composer**, the first part also contains steps to make sure you have the *appropriate version* of everything set up. If you're already up to date, you can skip to the second part right away.
If you don't want to install the required software natively, you may instead want to consider setting up [Docker](installation/environment/docker) as a pre-configured virtual environment.
diff --git a/app/pages/6.0/04.installation/02.environment/02.docker/docs.md b/app/pages/6.0/04.installation/02.environment/02.docker/docs.md
index 01a192d1..72fbf239 100644
--- a/app/pages/6.0/04.installation/02.environment/02.docker/docs.md
+++ b/app/pages/6.0/04.installation/02.environment/02.docker/docs.md
@@ -1,7 +1,6 @@
---
title: Docker
description: Docker is a containerization platform that helps maintain consistent behavior across different development and production environments.
-obsolete: true
---
If you don't already have a local development environment set up, this page will guide you through installing UserFrosting using Docker.
@@ -12,8 +11,25 @@ If you're familiar with PHP development, or already have PHP installed locally,
## Command Line Interface
-
-[plugin:content-inject](04.installation/_modular/cli)
+The [command line interface](installation/requirements/essential-tools-for-php#the-command-line-cli) will be required to perform most tasks in this guide. It's usage depends on your OS :
+
+#### MacOS
+If you're using MacOS, the **Terminal** is already installed on your computer. You'll find the app in `/System/Applications/Utilities/Terminal`.
+
+#### Linux
+Every Linux distro uses the command line. On Ubuntu for example, you can find a launcher for the terminal by clicking on the Activities item at the top left of the screen, then typing the first few letters of "terminal", "command", "prompt" or "shell".
+
+#### Windows
+The easiest way to setup a local dev environnement on Windows is through *Windows Subsystem for Linux* (WSL2). This is basically running Linux inside Windows. Best of both worlds! This also means most installation instructions for Windows you'll find on the internet won't work, as we're not technically *on* Windows, **we're on Ubuntu**. We'll instead use the Ubuntu installation instructions!
+
+See this guide for more detail on this process : [Set up a WSL development environment](https://learn.microsoft.com/en-us/windows/wsl/setup/environment). The gist of it is :
+
+1. Open *Windows Terminal*, which can be found in the [Microsoft Store](https://apps.microsoft.com/detail/9N0DX20HK701?hl=en-us&gl=US).
+2. Open the terminal and install WSL2 distro : `wsl --install`.
+3. During installation, enter a unix user with a password. Remember this password, you'll need it later!
+4. Restart Windows Terminal and open a new ***Ubuntu*** terminal. Each subsequent CLI usage on Windows will be from this Ubuntu terminal.
+
+When using Windows and WSL2, keep in mind your project files will be stored inside the Linux file system. For example, your project files will be in the Linux file system root directory (`\\wsl$\\home\\Project`), not the Windows file system root directory (`C:\Users\\Project or /mnt/c/Users//Project$`). See [Microsoft guide on file storage](https://learn.microsoft.com/en-us/windows/wsl/setup/environment#file-storage) for more information.
## Install Docker
First, you'll need to install Docker. Just follow the installation instructions from the Docker website:
@@ -21,10 +37,11 @@ First, you'll need to install Docker. Just follow the installation instructions
- [Windows (via WSL2)](https://docs.docker.com/desktop/install/windows-install/)
- [Linux](https://docs.docker.com/desktop/install/linux-install/)
-## Get UserFrosting
+## Get UserFrosting
For the next part, you'll need to use the command line. We'll use Composer (through a Docker image) to create an empty project, with the latest version of the UserFrosting skeleton, into a new `UserFrosting` subdirectory:
+
```bash
docker run --rm -it -v "$(pwd):/app" composer create-project userfrosting/userfrosting UserFrosting "^6.0-beta" --no-scripts --no-install --ignore-platform-reqs
```
@@ -34,10 +51,10 @@ docker run --rm -it -v "$(pwd):/app" composer create-project userfrosting/userfr
## Build Containers & Setup UserFrosting
-Now it's simply a matter of navigating to the directory containing the source code you just downloaded, building the containers, starting them, then installing UserFrosting.
+Now it's simply a matter of navigating to the directory containing the source code you just downloaded, building the containers, starting them, then installing UserFrosting.
1. Navigate to the directory:
-
+
```bash
cd UserFrosting
```
@@ -46,24 +63,24 @@ Now it's simply a matter of navigating to the directory containing the source co
> If you customized `UserFrosting` in the previous command, don't forget to change it in the command above.
2. Build each of the Docker Containers (this might take a while):
-
+
```bash
docker-compose build --no-cache
```
3. Copy the `.env` template
```bash
- cp app/.env.docker app/.env
+ cp app/.env.docker app/.env
```
4. Start each Docker Container:
-
+
```bash
docker-compose up -d
```
5. Set some directory permissions (you may have to enter your root password):
-
+
```bash
sudo touch app/logs/userfrosting.log
sudo chown -R $USER: .
@@ -71,22 +88,22 @@ Now it's simply a matter of navigating to the directory containing the source co
```
6. Install PHP dependencies:
-
+
```bash
docker-compose exec app composer update
```
7. Install UserFrosting (database configuration and migrations, creation of admin user, etc.). You'll need to provide info to create the admin user:
-
+
```bash
docker-compose exec app php bakery bake
```
Now visit [http://localhost:8080](http://localhost:8080) to see your UserFrosting homepage!
-You should see the default UserFrosting pages and be able to log in with the newly created admin account.
+You should see the default UserFrosting pages and be able to log in with the newly created admin account.
-
+
To stop the containers, run:
@@ -96,7 +113,7 @@ docker-compose stop
## Mailpit
-UserFrosting's default `docker-compose.yml` file contains a service entry for [Mailpit](https://github.com/axllent/mailpit). Mailpit intercepts emails sent by your application during local development and provides a convenient web interface so that you can preview your email messages in your browser.
+UserFrosting's default `docker-compose.yml` file contains a service entry for [Mailpit](https://github.com/axllent/mailpit). Mailpit intercepts emails sent by your application during local development and provides a convenient web interface so that you can preview your email messages in your browser.
While UserFrosting is running, you may access the Mailpit web interface at: [http://localhost:8025](http://localhost:8025).
diff --git a/app/pages/6.0/04.installation/02.environment/docs.md b/app/pages/6.0/04.installation/02.environment/docs.md
index 37ea6234..c5021d60 100644
--- a/app/pages/6.0/04.installation/02.environment/docs.md
+++ b/app/pages/6.0/04.installation/02.environment/docs.md
@@ -1,7 +1,6 @@
---
title: Dev Environment
description: Getting UserFrosting up and running in your development environment.
-obsolete: true
---
The process of setting up UserFrosting so that you can begin work in your [local development environment](background/develop-locally-serve-globally) is known as **installation**. This is a separate process from [deployment](going-live), when you push your fully developed application to a live server. Please be sure that you understand this distinction before proceeding further! UserFrosting is not like Wordpress, for example, where you can "install" directly to your production server.
diff --git a/app/pages/6.0/04.installation/_modular/cli/docs.md b/app/pages/6.0/04.installation/_modular/cli/docs.md
deleted file mode 100644
index ad4e6ad1..00000000
--- a/app/pages/6.0/04.installation/_modular/cli/docs.md
+++ /dev/null
@@ -1,22 +0,0 @@
-The [command line interface](installation/requirements/essential-tools-for-php#the-command-line-cli) will be required to perform most tasks in this guide. It's usage depends on your OS :
-
-#### MacOS
-If you're using MacOS, the **Terminal** is already installed on your computer. You'll find the app in `/System/Applications/Utilities/Terminal`.
-
-#### Linux
-Every Linux distro uses the command line. On Ubuntu for example, you can find a launcher for the terminal by clicking on the Activities item at the top left of the screen, then typing the first few letters of "terminal", "command", "prompt" or "shell".
-
-#### Windows
-The easiest way to setup a local dev environnement on Windows is through *Windows Subsystem for Linux* (WSL2). This is basically running Linux inside Windows. Best of both worlds! This also means most installation instructions for Windows you'll find on the internet won't work, as we're not technically *on* Windows, **we're on Ubuntu**. We'll instead use the Ubuntu installation instructions!
-
-See this guide for more detail on this process : [Set up a WSL development environment](https://learn.microsoft.com/en-us/windows/wsl/setup/environment). The gist of it is :
-
-1. Open *Windows Terminal*, which can be found in the [Microsoft Store](https://apps.microsoft.com/detail/9N0DX20HK701?hl=en-us&gl=US).
-2. Open the terminal and install WSL2 distro : `wsl --install`.
-3. During installation, enter a unix user with a password. Remember this password, you'll need it later!
-4. Restart Windows Terminal and open a new ***Ubuntu*** terminal. Each subsequent CLI usage on Windows will be from this Ubuntu terminal.
-
-When using Windows and WSL2, keep in mind your project files will be stored inside the Linux file system. For example, your project files will be in the Linux file system root directory (`\\wsl$\\home\\Project`), not the Windows file system root directory (`C:\Users\\Project or /mnt/c/Users//Project$`). See [Microsoft guide on file storage](https://learn.microsoft.com/en-us/windows/wsl/setup/environment#file-storage) for more information.
-
-> [!TIP]
-> Also see the [Get started using Visual Studio Code with Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-vscode) guide if you're using VSCode.
diff --git a/app/pages/6.0/04.installation/chapter.md b/app/pages/6.0/04.installation/chapter.md
index ce33884a..accb0ea0 100644
--- a/app/pages/6.0/04.installation/chapter.md
+++ b/app/pages/6.0/04.installation/chapter.md
@@ -1,11 +1,14 @@
---
title: Installation
description: Configuring a development environment with the necessary dependencies for UserFrosting, and using Bakery to get started on your first UserFrosting project.
-obsolete: true
---
#### Chapter 4
# Installation
-Now that we've gone over the basic structure of a UserFrosting based application, it's time to get things started. This chapter covers UserFrosting's environment dependencies and how to use your essential developer tools to start a UserFrosting project.
+Before you can build amazing web applications with UserFrosting, you need a development environment with the right tools. Setting this up might seem daunting if you're new to PHP development, but this chapter walks you through everything step by step.
+
+We'll cover UserFrosting's system requirements (PHP, Composer, Node.js), show you how to set up your development environment (whether you prefer native installation or Docker), and use the Bakery CLI tool to create your first project.
+
+By the end of this chapter, you'll have a fully functional UserFrosting installation running on your local machine, ready for development. Whether you're on Windows, macOS, or Linux, we'll get you up and running.
diff --git a/app/pages/6.0/05.troubleshooting/01.debugging/docs.md b/app/pages/6.0/05.troubleshooting/01.debugging/docs.md
index a5ec2e5d..45a8395d 100644
--- a/app/pages/6.0/05.troubleshooting/01.debugging/docs.md
+++ b/app/pages/6.0/05.troubleshooting/01.debugging/docs.md
@@ -1,7 +1,6 @@
---
title: Debugging
description: When your application "doesn't work", it's not always obvious where the problem lies. Modern web browsers come with a built-in tool for identifying problems in client-side code, as well as problems in the communication between your browser and the server.
-obsolete: true
---
As we mentioned in [Chapter 2](background/the-client-server-conversation), a web application is not a single piece of software. It consists of the server running PHP code, the client (browser) running Javascript and rendering the web page, and the conversation between the two parties. Things can go wrong in any of these three places.
@@ -182,7 +181,7 @@ Authorization debugging shows you a detailed breakdown of how UserFrosting's [au
#### Mail debugging
-The underlying [phpMailer](https://github.com/PHPMailer/PHPMailer) instance that we use can generate _very_ detailed information on the low-level processes involved when your code attempts to send email via SMTP. To have PHPMailer send this information to `app/logs/userfrosting.log`, set `debug.smtp` to `true` in your configuration file.
+The underlying [phpMailer](https://github.com/PHPMailer/PHPMailer) instance that we use can generate _very_ detailed information on the low-level processes involved when your code attempts to send email via SMTP. To have PHPMailer send this information to `app/logs/userfrosting.log`, set `mail.smtp_debug` to a non-zero value in your configuration file.
The level of detail can be specified with the `mail.smtp_debug` configuration value, using the values specified in the [PHPMailer documentation](https://github.com/PHPMailer/PHPMailer/blob/239d0ef38c1eea3e9f40bb949a9683aee9ca5c28/class.phpmailer.php#L318-L325):
diff --git a/app/pages/6.0/05.troubleshooting/02.getting-help/docs.md b/app/pages/6.0/05.troubleshooting/02.getting-help/docs.md
index e8e44e4d..f926cdbb 100644
--- a/app/pages/6.0/05.troubleshooting/02.getting-help/docs.md
+++ b/app/pages/6.0/05.troubleshooting/02.getting-help/docs.md
@@ -1,7 +1,6 @@
---
title: Getting Help
description: Don't be afraid to ask for help! Just, please be sure to read and understand our rules first.
-obsolete: true
---
## General tips for support
diff --git a/app/pages/6.0/05.troubleshooting/03.common-problems/docs.md b/app/pages/6.0/05.troubleshooting/03.common-problems/docs.md
index e8a0ed0b..57dfcea3 100644
--- a/app/pages/6.0/05.troubleshooting/03.common-problems/docs.md
+++ b/app/pages/6.0/05.troubleshooting/03.common-problems/docs.md
@@ -1,14 +1,10 @@
---
title: Common Problems
description: Commonly encountered issues when setting up, developing, or deploying a UserFrosting project.
-obsolete: true
---
## Installation
-
-
-
### I get a Node/npm error when running `php bakery bake`.
If installation of npm dependencies fails, see [npm](basics/requirements/essential-tools-for-php#npm) to ensure npm is correctly installed and updated. You may need to [change npm permissions](https://docs.npmjs.com/getting-started/fixing-npm-permissions).
@@ -25,7 +21,7 @@ This is an indication that asset build failed or you missed a step in the instal
### Installation went fine, except I don't see any styling on my home page. I am using Apache.
-UserFrosting uses a [dynamic routing system](asset-management/basic-usage) for serving assets in a development environment. For this to work on an Apache webserver, `mod_rewrite` needs to be enabled, **and** you need to give Apache permission to use the `.htaccess` file in `public/`.
+UserFrosting uses a [dynamic routing system](/asset-management/basic-usage) for serving assets in a development environment. For this to work on an Apache webserver, `mod_rewrite` needs to be enabled, **and** you need to give Apache permission to use the `.htaccess` file in `public/`.
#### Enabling `mod_rewrite`
diff --git a/app/pages/6.0/05.troubleshooting/chapter.md b/app/pages/6.0/05.troubleshooting/chapter.md
index 9d52fc65..03de93ae 100644
--- a/app/pages/6.0/05.troubleshooting/chapter.md
+++ b/app/pages/6.0/05.troubleshooting/chapter.md
@@ -1,7 +1,6 @@
---
title: Troubleshooting
description: Troubleshooting installation and your application.
-obsolete: true
---
#### Chapter 5
diff --git a/app/pages/6.0/06.sprinkles/01.sprinkles/docs.md b/app/pages/6.0/06.sprinkles/01.sprinkles/docs.md
index cc6d64bd..e95b3813 100644
--- a/app/pages/6.0/06.sprinkles/01.sprinkles/docs.md
+++ b/app/pages/6.0/06.sprinkles/01.sprinkles/docs.md
@@ -1,12 +1,11 @@
---
title: Basic concept
-description: ""
-obsolete: true
+description: Learn how sprinkles provide a modular system for extending UserFrosting without modifying core code.
---
-In previous versions of UserFrosting, you had to directly modify the files that come with the default installation in order to add your own functionality. For example, if you wanted to add a field to the user registration page, you had to actually modify `register.twig`. Or, if you wanted to add a new relation on the `User` class, you had to modify the actual `User.php` class that comes with UserFrosting.
+In earlier versions of UserFrosting, you had no choice—customizing meant editing core files. Want to add a field to the registration page? Modify `register.twig` directly. Need to extend the `User` class? Edit `User.php` itself. Every framework update risked breaking your work.
-Starting in version 4, this is no longer the case! **UserFrosting 4** introduced the **[Sprinkle system](structure/sprinkles)** as a way to completely isolate the code and content that you and your team produce from the core UserFrosting installation, as well as third-party code. **UserFrosting 5** took this a step further, by allowing Composer to manage sprinkles and decoupling even more functionality from the base install.
+**UserFrosting's sprinkle system** eliminates this problem entirely. Sprinkles let you extend, override, and customize functionality while keeping your code completely separate from the core framework. Updates become safe again—your customizations stay intact, isolated in your own sprinkle. Think of it like adding layers to a cake: each layer (sprinkle) builds on the previous one without destroying what's underneath.
## What is a "Sprinkle"?
@@ -27,7 +26,7 @@ As seen in the [App Structure Chapter](structure), sprinkles can be located anyw
### The Main Sprinkle
-Sprinkles are loaded in a specific order, defined by their dependencies, and entities of a given type in one sprinkle can extend entities of the same type in other sprinkles. The topmost sprinkle, usually your own project, is called the **main sprinkle**. All other sprinkles are called **depends sprinkles**.
+Sprinkles are loaded in a specific order, defined by their dependencies, and entities of a given type in one sprinkle can extend entities of the same type in other sprinkles. The topmost sprinkle, usually your own project, is called the **main sprinkle**. All other sprinkles are called **depends sprinkles**.
### Default Sprinkles
diff --git a/app/pages/6.0/06.sprinkles/02.content/docs.md b/app/pages/6.0/06.sprinkles/02.content/docs.md
index dc953eee..110fd930 100644
--- a/app/pages/6.0/06.sprinkles/02.content/docs.md
+++ b/app/pages/6.0/06.sprinkles/02.content/docs.md
@@ -1,9 +1,16 @@
---
title: Contents
-description: Detailed breakdown of a Sprinkle's contents.
-obsolete: true
+description: Detailed breakdown of a Sprinkle's contents and how each directory serves a specific purpose.
---
+Now that you understand what sprinkles are, let's explore what goes inside them. A sprinkle isn't just a random collection of files—it's an organized package where each directory has a specific purpose. Understanding this structure helps you know exactly where to put your code, templates, assets, and other resources.
+
+Think of a sprinkle like a well-organized kitchen: ingredients (code) go in the pantry, tools (assets) have their drawer, recipes (templates) are in the cookbook, and seasonings (configuration) sit on the spice rack. Everything has its place, making it easy to find what you need and add new items.
+
+This page details each directory in a sprinkle and explains what belongs there.
+
+## Directory Structure
+
Within each sprinkle, you will find any or all of the following directories and files:
```txt
@@ -30,7 +37,7 @@ Within each sprinkle, you will find any or all of the following directories and
├── vendor/
├── composer.json
├── package.json
-└── webpack.config.js
+└── vite.config.ts
```
> [!NOTE]
@@ -46,17 +53,17 @@ The sprinkle `composer.json` should also define the sprinkles this one depends o
### /package.json
-The `package.json` file is used for retrieving frontend dependencies via [npm](https://www.npmjs.com), like [Bootstrap](http://getbootstrap.com/). Dependencies specified in `package.json` will be downloaded to `/node_modules`.
+The `package.json` file is used for retrieving frontend dependencies via [npm](https://www.npmjs.com), like [Vue](https://vuejs.org/) components. Dependencies specified in `package.json` will be downloaded to `/node_modules`.
To download frontend dependencies, from the project root directory:
```bash
-$ php bakery webpack
+$ php bakery bake
```
-### /webpack.config.js
+### /vite.config.ts
-The `webpack.config.js` file is used for defining **asset entries** which can be referenced by templates. The advantage of using asset entries (as compared to referencing specific files) is that multiple files can be quickly referenced by the name of their entries. In production the individual files in each bundle are merged by Webpack Encore, reducing the number of HTTP requests that need to be made and thus reducing client latency and server load. See [Chapter 13](asset-management/asset-bundles) for more information about asset bundles.
+The `vite.config.ts` file is used for configuring **Vite** and defining how your assets are built and bundled. See [Chapter 13](/asset-management) for more information about asset management with Vite.
### /app/assets
@@ -84,11 +91,11 @@ The `logs` directory is used to store UserFrosting debug logs. This directory is
### /app/schema
-The `schema` directory contains the [request schema](routes-and-controllers/client-input/validation) for your sprinkle. Schema files in other sprinkles can be extended by using a custom loader.
+The `schema` directory contains the [request schema](/routes-and-controllers/client-input/validation) for your sprinkle. Schema files in other sprinkles can be extended by using a custom loader.
### /app/src
-The `src` directory contains the (preferably) [PSR-4](http://www.php-fig.org/psr/psr-4/) compatible PHP code for your sprinkle. This directory will contain your controllers, database models, [migrations](database/migrations), [routes](routes-and-controllers), [service providers](services), [data sprunjers](database/data-sprunjing), and any other custom classes that your sprinkle uses. This is where your sprinkle's Recipe will be found.
+The `src` directory contains the (preferably) [PSR-4](http://www.php-fig.org/psr/psr-4/) compatible PHP code for your sprinkle. This directory will contain your controllers, database models, [migrations](/database/migrations), [routes](/routes-and-controllers), [service providers](/services), [data sprunjers](/database/data-sprunjing), and any other custom classes that your sprinkle uses. This is where your sprinkle's Recipe will be found.
> [!NOTE]
> The content of `app/src/` can be customized and doesn't need to follow any strict convention.
@@ -99,11 +106,11 @@ The `storage` directory is used to store files managed by Filesystem service. Th
### /app/templates
-To separate content and logic, UserFrosting uses the popular [Twig](http://twig.symfony.com/) templating engine. Since Twig has its own system for [loading templates](http://twig.symfony.com/doc/api.html#built-in-loaders), UserFrosting builds upon this to allow overriding templates in sprinkles. See [Templating with Twig](templating-with-twig) for more information on how Twig is integrated into UserFrosting.
+To separate content and logic, UserFrosting uses the popular [Twig](http://twig.symfony.com/) templating engine. Since Twig has its own system for [loading templates](http://twig.symfony.com/doc/api.html#built-in-loaders), UserFrosting builds upon this to allow overriding templates in sprinkles. See [Templating with Twig](/templating-with-twig) for more information on how Twig is integrated into UserFrosting.
### /app/test
-The `test` directory is similar to `/src`, but for your [Tests](testing).
+The `test` directory is similar to `/src`, but for your [Tests](/testing).
### /app/.env
diff --git a/app/pages/6.0/06.sprinkles/03.recipe/docs.md b/app/pages/6.0/06.sprinkles/03.recipe/docs.md
index 72e82319..ac966c3e 100644
--- a/app/pages/6.0/06.sprinkles/03.recipe/docs.md
+++ b/app/pages/6.0/06.sprinkles/03.recipe/docs.md
@@ -1,21 +1,25 @@
---
title: The Sprinkle Recipe
-description: ""
-obsolete: true
+description: The recipe is your sprinkle's blueprint, telling UserFrosting how to integrate your code into the application.
---
-The Sprinkle Recipe dictates how your sprinkle is built, like a blueprint. UserFrosting services and framework will use the information from the recipe to initiate some services, and expose classes your sprinkle provides for other servicea to use.
+Every sprinkle needs a way to tell UserFrosting what it contains and how to use it. Without this information, UserFrosting wouldn't know which routes to register, which services to load, or how your sprinkle fits into the application. Imagine trying to bake a cake without a recipe—you'd have ingredients but no idea how to combine them.
-Each sprinkle **must have** a recipe. It's not possible for a sprinkle to exist without a recipe, as it won't be possible to expose its class and service to the framework. It's possible however to customize other sprinkles, as we'll see later on this page.
+The **Sprinkle Recipe** solves this problem. It's a simple PHP class that serves as your sprinkle's blueprint, declaring what your sprinkle provides: its name, location, routes, services, and dependencies. UserFrosting reads this recipe to integrate your sprinkle seamlessly into the application framework.
+
+Every sprinkle **must have** a recipe—it's how UserFrosting knows your sprinkle exists and what it contains. This page explains the recipe's structure and how to configure each part.
+
+> [!NOTE]
+> There is one exception where a sprinkle may not have a recipe: if it only provides static assets (like CSS or JS files) and no PHP code. In this case, the sprinkle does not need to be registered with a recipe since there are no dynamic components for the PHP runtime to consider. However, this is a rare case, and most sprinkles will require a recipe to function properly.
## The `SprinkleRecipe` Interface
The Sprinkle Recipe is a simple PHP class that provides standard methods which will be called by services to retrieve information about your sprinkle structure and the class it's registering. Every sprinkle recipe **MUST** implement the `UserFrosting\Sprinkle\SprinkleRecipe` interface. If you started from the [Skeleton](structure/introduction#the-app-skeleton-your-project-s-template), you already have a basic recipe.
-This interface requires you to implement the following method in your recipe:
+This interface requires you to implement the following method in your recipe:
- [`getName`](#name): Returns the name of the sprinkle.
-- [`getPath`](#path): Returns the path of the sprinkle.
-- [`getSprinkles`](#dependent-sprinkles): Returns an array of dependent sub-sprinkles recipe.
+- [`getPath`](#path): Returns the path of the sprinkle.
+- [`getSprinkles`](#dependent-sprinkles): Returns an array of dependent sub-sprinkles recipe.
- [`getRoutes`](#routes): Return an array of routes classes.
- [`getServices`](#services): Return an array of services classes.
@@ -24,9 +28,9 @@ This interface requires you to implement the following method in your recipe:
### Name
-This method returns the name identifier of the sprinkle. This name is mostly used in debug interfaces to identify resources and classes registered by the sprinkle.
+This method returns the name identifier of the sprinkle. This name is mostly used in debug interfaces to identify resources and classes registered by the sprinkle.
-The method should return a string. For example:
+The method should return a string. For example:
```php
public function getName(): string
@@ -52,7 +56,7 @@ This method returns the path of the sprinkle. This path should point where the `
├── vendor/
├── composer.json
├── package.json
-└── webpack.config.js
+└── vite.config.ts
```
...`getPath()` should point to `/app`, or in this case the parent directory of where the recipe file is located :
@@ -81,12 +85,11 @@ public function getSprinkles(): array
Core::class,
Account::class,
Admin::class,
- AdminLTE::class,
];
}
```
-Since `Admin` depends on `Core`, `Account` and `AdminLTE`, it's not mandatory to relist them in your recipe. In fact, the code above is equivalent to this, since the other one will be registered by `Admin`:
+Since `Admin` depends on `Core` and `Account`, it's not mandatory to relist them in your recipe. In fact, the code above is equivalent to this, since the other sprinkles will be registered by `Admin`:
```php
public function getSprinkles(): array
{
@@ -96,14 +99,13 @@ public function getSprinkles(): array
}
```
-This also mean removing `AdminLTE` as a dependency for example **cannot be done by simply removing it from your recipe**! It's impossible to remove `AdminLTE` without removing `Admin`, since `Admin` cannot work without its dependency.
+This also means removing a dependency **cannot be done by simply removing it from your recipe**! For example, you cannot remove `Account` without also removing `Admin`, since `Admin` depends on `Account`.
However, it also means the next example **is also equivalent**:
```php
public function getSprinkles(): array
{
return [
- AdminLTE::class,
Admin::class,
Account::class,
Core::class,
@@ -111,22 +113,21 @@ public function getSprinkles(): array
}
```
-Let's look at the process for the above code :
+Let's look at the process for the above code:
-1. AdminLTE will be loaded first. AdminLTE depends on Core first, and Account second. Core doesn't depend on anything. So **Core** is the first sprinkle loaded;
+1. Admin will be loaded first. Admin depends on Core first, and Account second. Core doesn't depend on anything. So **Core** is the first sprinkle loaded;
2. Account is then checked. It depends on Core, which is already loaded, so **Account** is the second loaded sprinkle;
-3. AdminLTE doesn't have any more dependencies, so **AdminLTE** is loaded in third;
-4. Admin is now checked. It depends on Core, Account and AdminLTE. All are already loaded, so everything is good. **Admin** is loaded fourth;
-5. This sprinkle's dependencies are all good, so it is loaded last.
+3. Admin doesn't have any more dependencies not already loaded, so **Admin** is loaded third;
+4. This sprinkle's dependencies are all good, so it is loaded last.
-Because of sprinkle dependencies, in all three examples the order will be `Core -> Account -> AdminLTE -> Admin -> YOUR APP`.
+Because of sprinkle dependencies, in all three examples the order will be `Core -> Account -> Admin -> YOUR APP`.
> [!TIP]
> An easy way to see the final order sprinkles are loaded is via the command line `php bakery sprinkle:list` command. The registered sprinkles will be displayed in the order they are registered.
### Routes
-Return an array of routes classes. More details about this will be explored in [Chapter 8 - Routes and Controllers](routes-and-controllers).
+Return an array of routes classes. More details about this will be explored in [Chapter 8 - Routes and Controllers](/routes-and-controllers).
For example, to register `MyRoutes` class:
```php
@@ -142,7 +143,7 @@ public function getRoutes(): array
Return an array of services definitions. These will be explored in [Chapter 7 - Dependency Injection](dependency-injection)
-Example:
+Example:
```php
public function getServices(): array
{
@@ -186,9 +187,9 @@ $bakery->run();
## Optional recipes
-The sprinkle recipe power comes from its modularity. To avoid having one huge recipe with empty content, optional features can be added only when necessary.
+The sprinkle recipe power comes from its modularity. To avoid having one huge recipe with empty content, optional features can be added only when necessary.
-The available sub-recipes includes:
+The available sub-recipes includes:
| Recipe | Features |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
@@ -218,7 +219,7 @@ class MyApp implements
### BakeryRecipe
Interface : `UserFrosting\Sprinkle\BakeryRecipe`
-Methods to implements :
+Methods to implements :
- `getBakeryCommands` : Return a list of [Bakery commands](cli/custom-commands) classes
**Example:**
@@ -252,11 +253,11 @@ Methods to implement :
### SeedRecipe
Interface : `UserFrosting\Sprinkle\Core\Sprinkle\Recipe\SeedRecipe`
-Methods to implement :
+Methods to implement :
- `getSeeds` : Return a list of [Seeds](database/seeding) classes
**Example:**
- ```php
+ ```php
public function getSeeds(): array
{
return [
@@ -270,7 +271,7 @@ Methods to implement :
### MiddlewareRecipe
Interface : `UserFrosting\Sprinkle\MiddlewareRecipe`
-Methods to implement :
+Methods to implement :
- `getMiddlewares` : Return a list of [Middlewares](advanced/middlewares) classes
**Example:**
@@ -287,7 +288,7 @@ Methods to implement :
### EventListenerRecipe
Interface : `UserFrosting\Event\EventListenerRecipe`
-Methods to implement :
+Methods to implement :
- `getEventListeners` : Allows to register [Event Listeners](advanced/events#listener)
**Example:**
@@ -314,7 +315,7 @@ Methods to implement :
### TwigExtensionRecipe
Interface : `UserFrosting\Sprinkle\Core\Sprinkle\Recipe\TwigExtensionRecipe`
-Methods to implement :
+Methods to implement :
- `getTwigExtensions` : Return a list of [Twig Extension](templating-with-twig/filters-and-functions#extending-twig-extensions) classes
**Example:**
@@ -341,15 +342,14 @@ In this case, two files need to be edited : `composer.json` and the Sprinkle Rec
2. Since changes were made to *composer.json*, composer need to be updated (`composer update`).
-3. In the Sprinkle Recipe, `Admin:class` can be removed from the `getSprinkles()` method:
- ```php
+3. In the Sprinkle Recipe, `Admin::class` can be removed from the `getSprinkles()` method:
+ ```php
public function getSprinkles(): array
{
return [
Core::class,
Account::class,
//Admin::class,
- AdminLTE::class,
];
}
```
@@ -369,9 +369,9 @@ In this case, instead of adding the dependent sprinkle (in `getSprinkles`), you
### Extending dependent recipe
-This method is best used when you want to *remove* a small number of resources from a dependent sprinkle. As with the previous method, if the dependent sprinkle is updated, you may need to manually update your code. If you want to only one resource from a dependent sprinkle, it's best to use the previous method to import one, than to remove everything else.
+This method is best used when you want to *remove* a small number of resources from a dependent sprinkle. As with the previous method, if the dependent sprinkle is updated, you may need to manually update your code. If you want to only one resource from a dependent sprinkle, it's best to use the previous method to import one, than to remove everything else.
-For example, you may want to remove all routes defined in the Account sprinkle :
+For example, you may want to remove all routes defined in the Account sprinkle :
```php
namespace UserFrosting\App;
@@ -393,6 +393,6 @@ class CustomAccount extends Account
}
```
-In this case, instead of depending on `Account` in `getSprinkles`, you'll add `CustomAccount` in your sprinkle `getSprinkles`. All other methods from `Account` will be included via `CustomAccount`.
+In this case, instead of depending on `Account` in `getSprinkles`, you'll add `CustomAccount` in your sprinkle `getSprinkles`. All other methods from `Account` will be included via `CustomAccount`.
You'll then have **two recipes** in your sprinkle, e.g.: `MyApp` and `CustomAccount`, side by side. `MyApp` will still be *main sprinkle*, referenced in `index.php` and `bakery`, since `CustomAccount` is a dependency of `MyApp`.
diff --git a/app/pages/6.0/06.sprinkles/04.customize/docs.md b/app/pages/6.0/06.sprinkles/04.customize/docs.md
index 540d4078..9ec0a146 100644
--- a/app/pages/6.0/06.sprinkles/04.customize/docs.md
+++ b/app/pages/6.0/06.sprinkles/04.customize/docs.md
@@ -1,7 +1,6 @@
---
title: Customizing Your Sprinkle
description: This guide walks you though the process of setting up your application by implementing a new sprinkle.
-obsolete: true
---
This guide assumes that you've already completed the [installation guide](installation) and successfully managed to get UserFrosting working in your [local development environment](background/develop-locally-serve-globally) using the [Skeleton](structure/introduction#the-app-skeleton-your-project-s-template). If not, please do that now - feel free to [ask for help](troubleshooting/getting-help) if you're running into trouble!
@@ -46,11 +45,10 @@ Original **composer.json**
"require": {
"php": "^8.1",
"ext-gd": "*",
- "userfrosting/framework": "^5.1",
- "userfrosting/sprinkle-core": "^5.1",
- "userfrosting/sprinkle-account": "^5.1",
- "userfrosting/sprinkle-admin": "^5.1",
- "userfrosting/theme-adminlte": "^5.1"
+ "userfrosting/framework": "^6.0",
+ "userfrosting/sprinkle-core": "^6.0",
+ "userfrosting/sprinkle-account": "^6.0",
+ "userfrosting/sprinkle-admin": "^6.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
@@ -100,11 +98,10 @@ Modified **composer.json**
"require": {
"php": "^8.1",
"ext-gd": "*",
- "userfrosting/framework": "^5.1",
- "userfrosting/sprinkle-core": "^5.1",
- "userfrosting/sprinkle-account": "^5.1",
- "userfrosting/sprinkle-admin": "^5.1",
- "userfrosting/theme-adminlte": "^5.1"
+ "userfrosting/framework": "^6.0",
+ "userfrosting/sprinkle-core": "^6.0",
+ "userfrosting/sprinkle-account": "^6.0",
+ "userfrosting/sprinkle-admin": "^6.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
@@ -162,7 +159,6 @@ use UserFrosting\Sprinkle\Admin\Admin;
use UserFrosting\Sprinkle\BakeryRecipe;
use UserFrosting\Sprinkle\Core\Core;
use UserFrosting\Sprinkle\SprinkleRecipe;
-use UserFrosting\Theme\AdminLTE\AdminLTE;
class Owlfancy implements
SprinkleRecipe,
diff --git a/app/pages/6.0/06.sprinkles/05.community/docs.md b/app/pages/6.0/06.sprinkles/05.community/docs.md
index 4d71fdea..a401aa21 100644
--- a/app/pages/6.0/06.sprinkles/05.community/docs.md
+++ b/app/pages/6.0/06.sprinkles/05.community/docs.md
@@ -1,7 +1,6 @@
---
title: Community Sprinkles
description: Sprinkles shared between projects are called community sprinkles.
-obsolete: true
---
One great thing about the **Sprinkle system** is its ability to wrap complete functionality inside a single package. This makes it a great tool to write your website isolated from the core UserFrosting code. It also means it's super easy to share sprinkles between projects... and with other members of the UserFrosting community! That's what we call a **community sprinkle**.
@@ -11,53 +10,45 @@ The best place to look for community sprinkles is on [GitHub](https://github.com
## Loading a community sprinkle
### Sprinkle recipe
-Once you've found a sprinkle you'd like to use in your own project, the first step is to `require` it in [your `composer.json`](sprinkles/customize#composer-json), along with the core UserFrosting sprinkles.
+Once you've found a sprinkle you'd like to use in your own project, the first step is to `require` it in [your `composer.json`](/sprinkles/customize#composer-json), along with the core UserFrosting sprinkles.
```json
"require": {
-...
- "userfrosting/theme-adminlte": "~5.1.0",
+ [...]
"owlfancy/owlery-sprinkle": "^0.1"
},
```
Next, add *their* recipe to *your* recipe, in the `getSprinkles()` method.
```php
-use Owlfancy\Sprinkle\Owlery ; //don't forget to include the sprinkle's namespace!
-...
+use Owlfancy\Sprinkle\Owlery; //don't forget to include the sprinkle's namespace!
+
+[...]
+
public function getSprinkles(): array
{
return [
Core::class,
Account::class,
Admin::class,
- AdminLTE::class, // include any base sprinkles you might need
- Owlery::class, // add the community sprinkle as well!
+ Owlery::class, // add the community sprinkle
];
}
```
> [!NOTE]
-> If the sprinkle does not include Javascript, you're done setting up--skip to the "Installing" section.
+> If the sprinkle does not include frontend assets (JavaScript, TypeScript, Vue components, etc.), you're done setting up--skip to the "Installing" section.
-### Javascript
-If the sprinkle includes Javascript, you will need to add it to both the "dependencies" [in your `package.json`](asset-management/webpack-encore#npm-and-packages-json)...
+### Frontend Assets
+If the sprinkle includes frontend assets like JavaScript, TypeScript, or Vue components, you will need to add it to the "dependencies" in your `package.json`:
```json
"dependencies": {
- "@userfrosting/sprinkle-admin": "~5.1.0",
- "@userfrosting/theme-adminlte": "~5.1.0",
+ "@userfrosting/sprinkle-admin": "^6.0",
+ "@userfrosting/theme-pink-cupcake": "^6.0",
"sprinkle-owlery":"github:owlfancy/sprinkle-owlery"
},
```
-...*and* to the "sprinkles" [in `/webpack.config.js`](asset-management/webpack-encore#webpack-encore-configuration).
-```js
-// List dependent sprinkles and local entries files
-const sprinkles = {
- AdminLTE: require('@userfrosting/theme-adminlte/webpack.entries'),
- Admin: require('@userfrosting/sprinkle-admin/webpack.entries'), // core sprinkles come included
- Owlery: require('sprinkle-owlery/webpack.entries'),// add any community sprinkles as well
-}
-```
-(We'll talk more about these files in the [Assets chapter](asset-management)).
+
+You may also need to configure Vite to properly handle the sprinkle's assets. See [Chapter 13](/asset-management) for more information about managing assets with Vite.
> [!TIP]
> In the `package.json` example above, we're loading the Userfrosting core sprinkles from npm, and the Owlery sprinkle from Github. Each community sprinkle decides where it is published, and should include this in their README.
@@ -70,12 +61,12 @@ php bakery bake
```
### Database (optional)
-If needed, [migrations](cli/commands#migrate) and [seeds](cli/commands#seed) can be run manually through Bakery.
+If needed, [migrations](cli/commands#migrate) and [seeds](cli/commands#seed) can be run manually through Bakery.
```txt
php bakery migrate
php bakery seed
```
-You can also use `php bakery migrate:status` and `php bakery seed:list` to check what migrations and seeds the sprinkle has added, and if any migrations have not yet been run.
+You can also use `php bakery migrate:status` and `php bakery seed:list` to check what migrations and seeds the sprinkle has added, and if any migrations have not yet been run.
> [!TIP]
> `php bakery bake` should run migrations automatically, but you can use the above commands later if you don't want to run a full `bake`.
@@ -88,13 +79,13 @@ You can also use `php bakery migrate:status` and `php bakery seed:list` to check
### Basic prep work
When you're ready to distribute your sprinkle, first use `composer update` to make sure it is up to date with the latest version of UserFrosting.
-Providing documentation and examples in a `README` file will encourage other devs to use your sprinkle. You should specify whether they need to add your sprinkle to `package.json`, if there are any seeds to run, and any other steps needed to fully set up.
+Providing documentation and examples in a `README` file will encourage other devs to use your sprinkle. You should specify whether they need to add your sprinkle to `package.json`, if there are any seeds to run, and any other steps needed to fully set up.
> [!TIP]
> As an example, if your sprinkle adds a new permission, anyone installing your sprinkle may need to manually add that permission to the appropriate roles through the UserFrosting UI.
Every sprinkle needs a valid `composer.json` file. This file is required to add any sort of class and PSR-4 definition to your sprinkle, so you already have one. Make sure it contains up-to-date information; your name and license details are always welcome. If you include a `type` key, be sure it's defined as `userfrosting-sprinkle` in your sprinkles `composer.json` file--but this is not required as of UserFrosting 5.
-We highly recommend publishing your community sprinkle on GitHub and adding it to [Packagist](https://packagist.org). This lets others include your sprinkle in `composer.json`, similar to how the default sprinkles are already defined.
+We highly recommend publishing your community sprinkle on GitHub and adding it to [Packagist](https://packagist.org). This lets others include your sprinkle in `composer.json`, similar to how the default sprinkles are already defined.
You may also have some extra steps depending on what features your sprinkle provides:
diff --git a/app/pages/6.0/06.sprinkles/chapter.md b/app/pages/6.0/06.sprinkles/chapter.md
index 3ed43d44..53f9c2b7 100644
--- a/app/pages/6.0/06.sprinkles/chapter.md
+++ b/app/pages/6.0/06.sprinkles/chapter.md
@@ -1,13 +1,14 @@
---
title: Sprinkles
description: Sprinkles are modular units of code and content that implement some feature or override some default behavior of UserFrosting.
-obsolete: true
---
#### Chapter 6
# Sprinkles
-The **sprinkle** system is a modular, flexible approach to separating core functionality from developer-added code and content.
+When building a web application, you face a fundamental challenge: **how do you extend and customize functionality without modifying the core codebase?** Directly editing framework code creates maintenance nightmares—every update risks breaking your changes, and sharing your work becomes nearly impossible.
-This is a powerful means of customizing your site without having to modify UserFrosting's core code. It also allows the UserFrosting community to share and integrate functionally cohesive units of code and content across projects.
+UserFrosting solves this with **sprinkles**—a modular system that lets you extend and override default behavior without touching core code. Think of sprinkles like toppings on ice cream: the base (core framework) stays intact, while you add your own flavors on top.
+
+This chapter explains how sprinkles work, how to create your own, and how to leverage community sprinkles to accelerate your development. You'll learn to build maintainable, modular applications that stay easy to update and share.
diff --git a/app/pages/6.0/07.dependency-injection/01.concept/docs.md b/app/pages/6.0/07.dependency-injection/01.concept/docs.md
index 60602e5a..55aa7d48 100644
--- a/app/pages/6.0/07.dependency-injection/01.concept/docs.md
+++ b/app/pages/6.0/07.dependency-injection/01.concept/docs.md
@@ -1,10 +1,17 @@
---
title: Understanding Dependency Injection
-description: Dependency Injection (DI) is the backbone of modern programming
-obsolete: true
+description: Dependency Injection (DI) is the backbone of modern programming and the key to writing testable, flexible code.
---
-[Dependency Injection](http://www.phptherightway.com/#dependency_injection) is one of the fundamental pillars of modern object-oriented software design - it is a prime example of the **D** in [**SOLID**](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)). The idea is that instead of creating objects _inside_ other objects, you create your "inner objects" (dependencies) separately and then _inject_ (by passing as an argument to the constructor or a setter method) them into the "outer object" (dependent).
+When objects create their own dependencies internally, you end up with rigid, tightly coupled code. Want to test a class? You can't—it's hardwired to specific implementations. Need to swap a dependency? You're rewriting the class. Want to mock something for testing? Impossible.
+
+**Dependency Injection (DI)** solves this elegantly. Instead of objects creating what they need, they declare their needs (dependencies), and someone else provides them. This simple shift—dependencies coming from outside rather than being created inside—makes your code testable, flexible, and maintainable. It's the **D** in [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) principles.
+
+Think of it like a restaurant: A bad restaurant has chefs who grow their own ingredients, make their own equipment, and handle everything internally (tightly coupled). A good restaurant has chefs who receive quality ingredients from suppliers (dependency injection). The chef focuses on cooking; the suppliers focus on ingredients. Everyone does their job better.
+
+This page explains dependency injection with concrete examples, showing you why it matters and how UserFrosting uses it throughout.
+
+## The Problem: Tight Coupling
For example, if you have class `Owl`:
@@ -44,7 +51,7 @@ class Owl
}
```
-now, we create our `Nest` externally to our `Owl`, and then pass it in:
+Now, we create our `Nest` externally to our `Owl`, and then pass it in:
```php
$nest = new Nest();
@@ -102,7 +109,7 @@ class Owl
}
```
-In the above example, it doesn't matter if `Owl` receives a `Nest` or an `ImprovedNest`, or even a `SuperDuperNest`, as long as they all obey the same contract defined by the `NestInterface`. Moreover, the `Owl` class can confidently call the `getSize()` method of the injected `$nest` property, because the interface ensures that method is available, no matter which implementation of the `NestInterface` it receives.
+In the above example, it doesn't matter if `Owl` receives a `Nest` or an `ImprovedNest`, or even a `SuperDuperNest`, as long as they all obey the same contract defined by the `NestInterface`. Moreover, the `Owl` class can confidently call the `getSize()` method of the injected `$nest` property, because the interface ensures that method is available, no matter which implementation of the `NestInterface` it receives.
Using interfaces to declare what kind of object a class is expected to receive, even if you don't plan to have multiple "nest" types, is a key element in *autowiring* that we'll see shortly.
diff --git a/app/pages/6.0/07.dependency-injection/02.the-di-container/docs.md b/app/pages/6.0/07.dependency-injection/02.the-di-container/docs.md
index 507f9e97..c7b0425f 100644
--- a/app/pages/6.0/07.dependency-injection/02.the-di-container/docs.md
+++ b/app/pages/6.0/07.dependency-injection/02.the-di-container/docs.md
@@ -1,11 +1,7 @@
---
title: The DI Container
description: The Dependency Injection (DI) container provides an elegant and loosely coupled way to make various services available globally in your application.
-obsolete: true
---
-
-### The Dependency Injection (DI) Container
-
The obvious issue with dependency injection, of course, is that it becomes harder to encapsulate functionality. Injecting a `Nest` into an `Owl`'s constructor requires that we build the Nest ourselves, instead of delegating it to the Owl. For classes with many, many dependencies, we can end up with a lot of code just to create an instance. Imagine an *Owl* that requires a *Nest*, that requires a *Tree*, that requires a *Forest*, etc.
As a more concrete example, let's look at the code required to create a new Monolog logging object:
@@ -30,18 +26,21 @@ Three main steps are required to create the object:
This is a lot of code to write just to create one measly object! It would be great if we could somehow encapsulate the creation of the object without creating tight couplings within the object itself.
-This is where the **dependency injection container (DIC)** comes into play. The DIC handles basic management of dependencies, encapsulating their creation into simple callbacks. We will call these callbacks **services**.
+### The Dependency Injection (DI) Container
+
+This is where the **dependency injection container (DIC)** comes into play. The DIC handles basic management of dependencies, encapsulating their creation into simple callbacks. We will call these callbacks **services**.
> [!NOTE]
> Dependency Injection (DI) and the Dependency Injection Container (DIC) are two separate concepts.
-> 1. dependency injection is a method for writing better code
-> 2. a container is a tool to help injecting dependencies
+> 1. Dependency injection (DI) is a method for writing better code
+> 2. Dependency injection container (DIC) is a tool to help injecting dependencies
+>
> You don't need a container to do dependency injection. However, a container can make injections easier.
UserFrosting uses [_PHP-DI 7_](https://php-di.org) as it's DIC implementation since it provides many powerful features that we rely on:
1. It creates dependencies lazily ("on demand"). Any service (and its dependencies) won't be created until the first time we access them.
-2. Once an object has been created in the container, the same object is returned in each subsequent call to the container.
+2. Once an object has been created in the container, the same object is returned in each subsequent call to the container.
3. It has the ability to automatically create and inject dependencies.
4. It has powerful Slim 4 integration.
@@ -77,16 +76,17 @@ $owl = $container->get(Owl::class);
It's very simple, doesn't require any configuration, and it just works !
-> Autowiring is an exotic word that represents something very simple: the ability of the container to automatically create and inject dependencies.
> [!NOTE]
+> Autowiring is an exotic word that represents something very simple: the ability of the container to automatically create and inject dependencies.
+>
> You can learn more about autowiring in the [PHP-DI Documentation](https://php-di.org/doc/autowiring.html)
### Service Providers & Definitions
Sometimes classes might be a bit more complex to instantiate, especially third party ones (eg. the logger object from before). Or you might want to use a different class based on some configuration value. You might also want a class to be replaced by another one (eg. our `ImprovedNest`). In these cases, autowiring cannot be used. This is where PHP-DI **definition** comes handy. PHP-DI loads the definitions you have written and uses them like instructions on how to create objects.
-UserFrosting sets up its services through **service provider** classes. Each sprinkle can define as many service providers as it needs and register them in the [Recipe](dependency-injection/adding-services). For example, the Services Provider class for the previous `Logger` example would look like this:
+UserFrosting sets up its services through **service provider** classes. Each sprinkle can define as many service providers as it needs and register them in the [Recipe](/dependency-injection/adding-services). For example, the Services Provider class for the previous `Logger` example would look like this:
```php
use Monolog\Formatter\LineFormatter;
@@ -106,7 +106,7 @@ class LoggerServicesProvider implements ServicesProviderInterface
return $logger;
},
-
+
StreamHandler::class => function () {
// 'userfrosting.log' could be fetched from a Config service here, for example.
return new StreamHandler('userfrosting.log');
@@ -121,7 +121,7 @@ class LoggerServicesProvider implements ServicesProviderInterface
This definition uses the [PHP-DI factories](https://php-di.org/doc/php-definitions.html#factories) syntax. From the PHP-DI documentation:
> Factories are PHP callables that return the instance. They allow to easily define objects lazily, i.e. each object will be created only when actually needed (because the callable will be called when actually needed).
->
+>
> Just like any other definition, factories are called once and the same result is returned every time the factory needs to be resolved.
>
> Other services can be injected via type-hinting (as long as they are registered in the container or autowiring is enabled).
@@ -142,7 +142,7 @@ Earlier we discussed the benefits of using interfaces, as the constructor can ac
public function __construct(NestInterface $nest) // Accept both `Nest` and `ImprovedNest`
```
-In this case, _Autowiring_ can't help us since the `NestInterface` cannot be instantiated: it's not a class, it's an interface! In this case, PHP Definitions can be used to match the interface with the correct class we want, using either a factory, or the [Autowired object](https://php-di.org/doc/php-definitions.html#autowired-objects) syntax:
+In this case, _Autowiring_ can't help us since the `NestInterface` cannot be instantiated: it's not a class, it's an interface! In this case, PHP Definitions can be used to match the interface with the correct class we want, using either a factory, or the [Autowired object](https://php-di.org/doc/php-definitions.html#autowired-objects) syntax:
```php
return [
@@ -151,7 +151,7 @@ return [
];
```
-The "nest of choice" can now be selected in the service provider. It could also be selected using another kind of logic, for example using a `Config` service and the new for PHP 8.0 [match expression](https://www.php.net/manual/en/control-structures.match.php):
+The "nest of choice" can now be selected in the service provider. It could also be selected using another kind of logic, for example using a `Config` service and the new for PHP 8.0 [match expression](https://www.php.net/manual/en/control-structures.match.php):
```php
return [
// Inject Config to decide which nest to use, and the Container to get the actual class
@@ -165,7 +165,7 @@ return [
];
```
-But why are interfaces really needed? If `ImprovedNest` extends `Nest`, wouldn't the constructor accept an `ImprovedNest` anyway if you type-hinted against `Nest`? Well, yes... But it won't work the other way around. For example :
+But why are interfaces really needed? If `ImprovedNest` extends `Nest`, wouldn't the constructor accept an `ImprovedNest` anyway if you type-hinted against `Nest`? Well, yes... But it won't work the other way around. For example :
```php
@@ -181,7 +181,7 @@ class AcceptNest {
$improvedNest = $this->ci->get(Nest::class); // Return `ImprovedNest`, because service is configured this way
$test = new AcceptNest($improvedNest); // Works, ImprovedNest is a subtype of Nest
-// This wont
+// This wont
class AcceptImprovedNest {
public function __construct(protected ImprovedNest $nest)
diff --git a/app/pages/6.0/07.dependency-injection/03.default-services/docs.md b/app/pages/6.0/07.dependency-injection/03.default-services/docs.md
index e56a19e3..0906b916 100644
--- a/app/pages/6.0/07.dependency-injection/03.default-services/docs.md
+++ b/app/pages/6.0/07.dependency-injection/03.default-services/docs.md
@@ -1,10 +1,9 @@
---
title: Default Services
description: UserFrosting's default services provide most of the tools needed to build a basic web application.
-obsolete: true
---
-As mentioned in the last section, each sprinkle can set up its own services through **service providers**. The [bundled sprinkles](structure/sprinkles#bundled-sprinkles) set up many services that are essential to UserFrosting's functionality. These services can be found in the `src/ServicesProvider/` subdirectories in each Sprinkle's directory.
+As mentioned in the last section, each sprinkle can set up its own services through **service providers**. The [bundled sprinkles](structure/sprinkles#bundled-sprinkles) set up many services that are essential to UserFrosting's functionality. These services can be found in the `src/ServicesProvider/` subdirectories in each Sprinkle's directory.
But this is just the tip of the iceberg, since _Autowiring_ is also used throughout the source code to inject other types of classes pretty much everywhere.
@@ -16,11 +15,11 @@ Third party services are also used directly throughout the code. They can be inj
### `UserFrosting\Alert\AlertStream`
-This service handles the [alert message stream](advanced/alert-stream), sometimes known as "flash messages". (See Chapter 18 for more information.)
+This service handles the [alert message stream](/advanced/alert-stream), sometimes known as "flash messages". (See Chapter 18 for more information.)
### `Illuminate\Cache\Repository as Cache`
-Creates an instance of a Laravel [Cache](https://laravel.com/docs/8.x/cache). See [Chapter 17](advanced/caching) for more information.
+Creates an instance of a Laravel [Cache](https://laravel.com/docs/8.x/cache). See [Chapter 17](/advanced/caching) for more information.
### `UserFrosting\Config\Config`
@@ -30,32 +29,44 @@ The `config` service also builds the `site.uri.public` config variable from the
### `UserFrosting\Sprinkle\Core\Csrf\CsrfGuard`
-Constructs the [CSRF Guard](https://github.com/slimphp/Slim-Csrf) middleware, which mitigates cross-site request forgery attacks on your users. See [Chapter 2](background/security) for more information on security features.
+Constructs the [CSRF Guard](https://github.com/slimphp/Slim-Csrf) middleware, which mitigates cross-site request forgery attacks on your users. This is wrapped by `CsrfGuardMiddleware` and can be globally disabled via the `csrf.enabled` config. See [Chapter 2](background/security) for more information on security features.
-### `UserFrosting\Sprinkle\Core\Database\Migrator\Migrator`
+### `Illuminate\Database\Capsule\Manager` (Capsule)
-Creates an instance of `Migrator`, which runs your database [migrations](database/migrations).
+Creates the Eloquent ORM database connection manager. This service handles all database connections defined in your configuration and provides access to the query builder and schema builder. Most of the time you'll interact with database models rather than the Capsule directly.
-### `UserFrosting\Sprinkle\Core\Log\DebugLogger`
+Related services:
+- `Illuminate\Database\Connection` - The active database connection
+- `Illuminate\Database\Schema\Builder` - For schema operations and migrations
-Monolog `Logger` object for sending debug print statements and data to `logs/debug.log`. Can also be accessed via the [`Debug` facade](troubleshooting/debugging#debug-statements).
+### `UserFrosting\Sprinkle\Core\Database\Migrator\MigrationLocatorInterface`
-### `UserFrosting\Sprinkle\Core\Log\ErrorLogger`
+Provides access to the database migration system. Use the `Migrator` class to run migrations via the `migrate` Bakery command or programmatically. See [database migrations](database/migrations) for more information.
-Monolog `Logger` object for sending non-fatal error information from custom error handlers to `logs/userfrosting.log`.
+### `UserFrosting\Sprinkle\Core\Log\DebugLoggerInterface`
-### `UserFrosting\Sprinkle\Core\Log\MailLogger`
+Monolog `Logger` object for sending debug print statements and data to `logs/debug.log`. Inject via the `DebugLoggerInterface` for proper dependency injection.
-Monolog `Logger` object for sending detailed SMTP mail server information from the `mailer` service to `logs/userfrosting.log`. Mail logging will only occur if `debug.smtp` is set to `true`.
+### `UserFrosting\Sprinkle\Core\Log\ErrorLoggerInterface`
-### `UserFrosting\Sprinkle\Core\Log\QueryLogger`
+Monolog `Logger` object for sending non-fatal error information from custom error handlers to `logs/userfrosting.log`. Inject via the `ErrorLoggerInterface`.
-Monolog `Logger` object for logging successfully completed database queries to `logs/userfrosting.log`.
+### `UserFrosting\Sprinkle\Core\Log\MailLoggerInterface`
+
+Monolog `Logger` object for sending detailed SMTP mail server information from the `Mailer` service to `logs/userfrosting.log`. Inject via the `MailLoggerInterface`.
+
+### `UserFrosting\Sprinkle\Core\Log\QueryLoggerInterface`
+
+Monolog `Logger` object for logging successfully completed database queries to `logs/userfrosting.log`. Query logging only occurs when `debug.queries` is set to `true` in the configuration. Inject via the `QueryLoggerInterface`.
### `UserFrosting\Sprinkle\Core\Mail\Mailer`
Creates an instance of `Mailer`, which serves as a UF-compatible wrapper for a [PHPMailer](https://github.com/PHPMailer/PHPMailer) object. See [Chapter 14](mail) for more information.
+### `League\CommonMark\ConverterInterface`
+
+Provides the CommonMark Markdown parser for converting Markdown to HTML. UserFrosting uses this with GitHub Flavored Markdown support. Sprinkles can register custom Markdown extensions by implementing the `MarkdownExtensionRecipe` interface.
+
### `UserFrosting\Sprinkle\Core\Throttle\Throttler`
Creates a `Throttler` object, which handles [request throttling](routes-and-controllers/client-input/throttle) for different routes. This service will automatically register any throttling rules defined in the `throttles` key of your configuration.
@@ -80,15 +91,29 @@ extension (`UserFrosting\Sprinkle\Core\Twig\CoreExtension`), which provides some
See [Templating with Twig](templating-with-twig) for more information about Twig and the custom functions, filters, and variables that UserFrosting defines.
+### `UserFrosting\Sprinkle\Core\Twig\CoreExtension`
+UserFrosting's core Twig extensions, which provide additional functions, filters, and global variables.
+
+See [Templating with Twig](templating-with-twig) for more information about Twig and the custom functions, filters, and variables that UserFrosting defines.
+
+### `UserFrosting\ViteTwig\ViteManifestInterface`
+
+Provides integration with Vite for frontend asset management. This service reads the Vite manifest file and provides methods to generate asset URLs with proper cache busting. See [Chapter 13](asset-management) for more information.
+
+### `Symfony\WebpackEncoreBundle\Asset\EntrypointLookupInterface`
+
+Legacy Webpack Encore integration service. If you're using Webpack instead of Vite for asset management, this service provides access to the Webpack manifest
+
+
### `UserFrosting\I18n\Translator`
-Sets up the `Translator` object (`UserFrosting\I18n\Translator`) for translation, localization, and internationalization of your site's contents. See [Chapter 17](i18n) for more information.
+Sets up the `Translator` object (`UserFrosting\I18n\Translator`) for translation, localization, and internationalization of your site's contents. See [Chapter 17](/i18n) for more information.
### `UserFrosting\UniformResourceLocator\ResourceLocatorInterface`
An instance of our own [Uniform Resource Locator class](https://github.com/userfrosting/framework/tree/5.1/src/UniformResourceLocator#readme), which provides a unified method of accessing Sprinkle entities via streams.
-See [Chapter 18](advanced/locator) for more information.
+See [Chapter 18](/advanced/locator) for more information.
### `UserFrosting\Sprinkle\SprinkleManager`
@@ -100,6 +125,10 @@ The `SprinkleManager` can be used to get a list of all sprinkles currently loade
Creates an instance of `Authenticator`, which handles user authentication and logins. See [Chapter 10](users/user-accounts#authentication-and-authorization) for more information.
+### `UserFrosting\Sprinkle\Account\Log\AuthLoggerInterface`
+
+Monolog `Logger` object for logging detailed information about access control checks. See [Chapter 10](users/access-control) for more information about access control. Note that access control checks will only be logged if `debug.auth` is set to `true` in the configuration. Inject via the `AuthLoggerInterface`
+
### `UserFrosting\Sprinkle\Account\Authenticate\AuthGuard`
The `AuthGuard` middleware, which is bound to routes which require authentication to access ("protected routes"). See [Chapter 10](users/user-accounts#authentication-and-authorization) for more information.
@@ -112,16 +141,40 @@ The `GuestGuard` middleware, which is bound to routes that require a guest (non
Monolog `Logger` object for logging detailed information about access control checks. See [Chapter 10](users/access-control) for more information about access control. Note that access control checks will only be logged if `debug.auth` is set to `true` in the configuration.
-### `UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager`
+### `UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager`
*Associated Interface : `UserFrosting\Sprinkle\Account\Authorize\AuthorizationManagerInterface`*
-Creates an instance of `AuthorizationManager`, which handles access control checks via the `checkAccess` method. This service also defines several default access condition callbacks. More information, and a complete list of default access condition callbacks, can be found in [Chapter 10](users/access-control).
+The `AuthorizationManager` handles access control checks for protected routes. It uses the [Role-Based Access Control (RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control) system, which is based on user roles and permissions. See [Chapter 10](users/access-control) for more information about access control.
+
+### `UserFrosting\Sprinkle\Account\Authenticate\Interfaces\MFAProvider`
+
+Provides Multi-Factor Authentication (MFA) functionality. By default, this uses email-based OTP (One-Time Password) codes via the `EmailOtpProvider` implementation. You can replace this service to use other MFA methods like TOTP (Time-Based OTP) for authenticator apps or SMS.
+
+### `UserFrosting\Sprinkle\Account\Authenticate\Interfaces\EmailVerificationProvider`
+
+Handles email verification for new user registrations and email changes. By default, this uses the same email-based OTP system as MFA via the `EmailOtpProvider` implementation.
+
+### `UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface`
+
+While not technically a service, UserFrosting maps model interfaces to their implementations via the `ModelsService`. This allows you to override model classes by decorating the service. Other model interfaces include:
+- `ActivityInterface`
+- `GroupInterface`
+- `PermissionInterface`
+- `RoleInterface`
+- `UserVerificationInterface`
+
+### `UserFrosting\Sprinkle\Account\Authenticate\Interfaces\HasherInterface`
+
+Creates an instance of `Hasher`, which handles password hashing and validation. Inject via the `HasherInterface`.
+
+### `UserFrosting\Sprinkle\Core\I18n\SiteLocaleInterface`
-### `UserFrosting\Sprinkle\Account\Authenticate\Hasher`
+Provides the current site locale settings. The Account Sprinkle overrides the Core implementation to provide user-specific locale preferences.
-Creates an instance of `Hasher`, which handles password hashing and validation.
+### `UserFrosting\Sprinkle\Account\Log\UserActivityLoggerInterface`
+Sets up a Monolog `Logger` object which logs user activities to the `activities` database table using `UserActivityDatabaseHandler`. This makes it easy to track user actions throughout your application. Inject via the `UserActivityLoggerInterface`
### `UserFrosting\Sprinkle\Account\Log\UserActivityLogger`
Sets up a Monolog `Logger` object, which uses `UserFrosting\Sprinkle\Account\Log\UserActivityDatabaseHandler` and `UserFrosting\Sprinkle\Account\Log\UserActivityProcessor` to allow logging of user activities to the `activities` database table. Monolog makes it easy to swap to other storage solutions such as Redis or Elastic Search.
diff --git a/app/pages/6.0/07.dependency-injection/04.adding-services/docs.md b/app/pages/6.0/07.dependency-injection/04.adding-services/docs.md
index 052a997a..8b1fadaf 100644
--- a/app/pages/6.0/07.dependency-injection/04.adding-services/docs.md
+++ b/app/pages/6.0/07.dependency-injection/04.adding-services/docs.md
@@ -1,12 +1,11 @@
---
title: Adding Services
description: You may extend UserFrosting's default services for additional functionality, or define completely new services in your Sprinkles.
-obsolete: true
---
You'll probably want to create your own services to modularize certain aspects of your own project. For example, if your application needs to interact with some third-party API like Google Maps, you might create a `MapBuilder` class that encapsulates all of that functionality. This is a cleaner and more manageable alternative to simply stuffing all of your code directly into your controller classes.
-If you want to use a single instance of `MapBuilder` throughout your application, you'll probably end up defining it as a service. To do this, you'll need to create a new `MapBuilderService` class in your site sprinkle and register it in your [Sprinkle Recipe](sprinkles/recipe#services).
+If you want to use a single instance of `MapBuilder` throughout your application, you'll probably end up defining it as a service. To do this, you'll need to create a new `MapBuilderService` class in your site sprinkle and register it in your [Sprinkle Recipe](/sprinkles/recipe#services).
You can actually create one big service provider for all your services, but it's best to create different provider classes for each service. This makes it easier to test and debug each of your services. It also makes things easier if you need to extend or disable a service in another sprinkle down the road. With this setup, each service resides in its own provider class instead of the global `ServiceProvider` class. For example:
@@ -21,7 +20,7 @@ app
### Create your service
-First, we'll create the service class. This class **must** implement the `UserFrosting\ServicesProvider\ServicesProviderInterface` interface. It must contain the `register` method, which returns an array of [service definitions](dependency-injection/the-di-container#service-providers-definitions).
+First, we'll create the service class. This class **must** implement the `UserFrosting\ServicesProvider\ServicesProviderInterface` interface. It must contain the `register` method, which returns an array of [service definitions](/dependency-injection/the-di-container#service-providers-definitions).
**app/src/ServicesProvider/MapBuilderService.php**
@@ -76,7 +75,7 @@ MapBuilder::class => function (Config $config) {
### Register your service
-The next step is to tell UserFrosting to load your service in your [Sprinkle Recipe](sprinkles/recipe#getservices). To do so, you only need to list all the service providers you want to automatically register inside the `$getServices` property of your sprinkle class :
+The next step is to tell UserFrosting to load your service in your [Sprinkle Recipe](/sprinkles/recipe#getservices). To do so, you only need to list all the service providers you want to automatically register inside the `$getServices` property of your sprinkle class:
**app/src/MyApp.php**
@@ -87,7 +86,7 @@ namespace UserFrosting\Sprinkle\Site;
use UserFrosting\Sprinkle\Site\ServicesProvider\MapBuilderService;
use UserFrosting\Sprinkle\Site\ServicesProvider\FooService;
-use UserFrosting\System\Sprinkle\Sprinkle;
+use UserFrosting\Sprinkle\SprinkleRecipe;
class MyApp implements SprinkleRecipe
{
@@ -105,4 +104,4 @@ class MyApp implements SprinkleRecipe
}
```
-That's it! Behind the scenes, UserFrosting will register every definition from each service provider with the DI container, following the sprinkle [dependency tree](sprinkles/recipe#dependent-sprinkles) during the [application lifecycle](advanced/application-lifecycle).
+That's it! Behind the scenes, UserFrosting will register every definition from each service provider with the DI container, following the sprinkle [dependency tree](/sprinkles/recipe#dependent-sprinkles) during the [application lifecycle](/advanced/application-lifecycle).
diff --git a/app/pages/6.0/07.dependency-injection/05.extending-services/docs.md b/app/pages/6.0/07.dependency-injection/05.extending-services/docs.md
index ed839709..4c368710 100644
--- a/app/pages/6.0/07.dependency-injection/05.extending-services/docs.md
+++ b/app/pages/6.0/07.dependency-injection/05.extending-services/docs.md
@@ -1,7 +1,6 @@
---
title: Extending Existing Services
description: You may extend UserFrosting's default services for additional functionality, or define completely new services in your sprinkles.
-obsolete: true
---
PHP-DI allows us to extend services that were defined previously, for example in another sprinkle, using [decorators](https://php-di.org/doc/definition-overriding.html#decorators).
diff --git a/app/pages/6.0/07.dependency-injection/chapter.md b/app/pages/6.0/07.dependency-injection/chapter.md
index 54120c5e..5ce1eb62 100644
--- a/app/pages/6.0/07.dependency-injection/chapter.md
+++ b/app/pages/6.0/07.dependency-injection/chapter.md
@@ -1,13 +1,16 @@
---
title: Dependency Injection
description: Services are a way to allow objects that perform specific, commonly used functions to be reused throughout your application. Mail, logging, and authorization are all examples of services.
-obsolete: true
---
#### Chapter 7
# Dependency Injection
-Dependency injection is one of the fundamental pillars of modern object-oriented software design. It is used extensively throughout UserFrosting to glue all services together while maintaining great flexibility to extend the basics functionalities of UserFrosting to create your own project.
+Modern applications need shared functionality—sending emails, logging events, authorizing users. But how do you make these services available throughout your code without creating tight coupling and making your code untestable?
-Services are a way to allow objects that perform specific, commonly used functions to be reused throughout your application. Mail, logging, and authorization are all examples of services. The **Dependency Injection (DI) Container** provides an elegant and loosely coupled way to make various services available globally in your application.
+**Dependency Injection (DI)** is the solution. Instead of objects creating their own dependencies, they declare what they need, and a **DI Container** provides them. This makes code modular, testable, and flexible—you can easily swap implementations without changing dependent code.
+
+UserFrosting uses dependency injection extensively to wire together services like mail, logging, database access, and authorization. Understanding the DI container is key to extending UserFrosting's functionality and building well-architected applications.
+
+This chapter explains dependency injection concepts, how UserFrosting's DI container works, the services available by default, and how to add or customize services for your own needs.
diff --git a/app/pages/6.0/08.routes-and-controllers/01.introduction/docs.md b/app/pages/6.0/08.routes-and-controllers/01.introduction/docs.md
index 9d14d680..d6868d37 100644
--- a/app/pages/6.0/08.routes-and-controllers/01.introduction/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/01.introduction/docs.md
@@ -1,10 +1,17 @@
---
title: Introduction
description: If you're new to object-oriented programming, you may not be familiar with the MVC pattern, a popular and very flexible design paradigm for scalable, easily maintained web applications.
-obsolete: true
---
-UserFrosting is built to follow the [Model-View-Controller (MVC)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) design paradigm. If you come from a "traditional" PHP background, you may be used to seeing code that looks like this:
+Imagine building a web application where all your code lives in single files—database queries mixed with HTML output, business logic tangled with form processing, everything in one giant mess. This is **spaghetti code**, and it's a maintenance nightmare. Want to change the database? You're rewriting HTML. Need to update the UI? You're touching business logic. Testing becomes impossible.
+
+The **Model-View-Controller (MVC)** pattern solves this by separating concerns into three distinct layers: the **model** (data and business logic), the **view** (presentation/HTML), and the **controller** (coordinates between model and view). Each layer has one job, making your code easier to understand, test, and maintain.
+
+UserFrosting embraces MVC fully. Models use [Eloquent](database) for database interactions, views use [Twig templates](templating-with-twig) for clean HTML generation, and controllers (built on [Slim 4](https://www.slimframework.com/)) tie everything together. This chapter focuses on controllers—the starting point for adding new features to your application.
+
+## Understanding Spaghetti Code
+
+If you come from a "traditional" PHP background, you may be used to seeing code that looks like this:
**users.php**
```php
diff --git a/app/pages/6.0/08.routes-and-controllers/02.REST/01.restful-endpoints/docs.md b/app/pages/6.0/08.routes-and-controllers/02.REST/01.restful-endpoints/docs.md
index 3b676470..e8409f7a 100644
--- a/app/pages/6.0/08.routes-and-controllers/02.REST/01.restful-endpoints/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/02.REST/01.restful-endpoints/docs.md
@@ -1,7 +1,6 @@
---
title: RESTful Endpoints
description: Together, a specific url and method are commonly referred to as an **endpoint**. It is important to use a consistent, RESTful approach to the URLs and methods you choose for each endpoint.
-obsolete: true
---
A RESTful url should represent a _thing_, not an _action_. We want to avoid putting any verbs in the name of the url. Instead, the action should be defined by the HTTP method. For example:
diff --git a/app/pages/6.0/08.routes-and-controllers/02.REST/02.restful-responses/docs.md b/app/pages/6.0/08.routes-and-controllers/02.REST/02.restful-responses/docs.md
index 46c34748..bab9977b 100644
--- a/app/pages/6.0/08.routes-and-controllers/02.REST/02.restful-responses/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/02.REST/02.restful-responses/docs.md
@@ -1,7 +1,6 @@
---
title: RESTful Responses
description: Your responses should use headers and status codes consistent with the HTTP specifications. This section lists the HTTP codes commonly used by UserFrosting.
-obsolete: true
---
## RESTful Responses
@@ -95,7 +94,7 @@ use UserFrosting\Sprinkle\Core\Exceptions\NotFoundException;
public function updateField()
{
$user = // ...
-
+
// Will cause a 404 response
if ($user === null) {
throw new NotFoundException();
diff --git a/app/pages/6.0/08.routes-and-controllers/02.REST/docs.md b/app/pages/6.0/08.routes-and-controllers/02.REST/docs.md
index 733333e0..5b77b095 100644
--- a/app/pages/6.0/08.routes-and-controllers/02.REST/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/02.REST/docs.md
@@ -1,7 +1,6 @@
---
title: RESTful Design
description: Representational State Transfer (REST) is a design paradigm for efficient, scalable communication between clients and the server.
-obsolete: true
---
Before we talk about the application itself, let's talk about how the client gets to the application in the first place: by making a **request**. A request consists of a few main components:
diff --git a/app/pages/6.0/08.routes-and-controllers/03.front-controller/docs.md b/app/pages/6.0/08.routes-and-controllers/03.front-controller/docs.md
index e6b63190..42d00c5b 100644
--- a/app/pages/6.0/08.routes-and-controllers/03.front-controller/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/03.front-controller/docs.md
@@ -1,7 +1,6 @@
---
title: Front Controller
description: The front controller consists of the route definitions that UserFrosting uses to process incoming requests from the client.
-obsolete: true
---
The front controller is a collective term for the **routes** that your web application defines for its various **endpoints**. This is how UserFrosting links URLs and methods to your application's code.
@@ -11,7 +10,7 @@ Sprinkles define their routes in classes and register them in their Recipe. Ther
The following is an example of a `GET` route:
```php
-$app->get('/api/users/u/{username}', function (string $username, Request $request, Response $response, array $args)
+$app->get('/api/users/u/{username}', function (string $username, Request $request, Response $response, array $args)
{
$getParams = $request->getQueryParams();
diff --git a/app/pages/6.0/08.routes-and-controllers/04.controller-classes/docs.md b/app/pages/6.0/08.routes-and-controllers/04.controller-classes/docs.md
index c31c56e6..d317cfb9 100644
--- a/app/pages/6.0/08.routes-and-controllers/04.controller-classes/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/04.controller-classes/docs.md
@@ -1,7 +1,6 @@
---
title: Controller Classes
description: Controller classes allow you to easily separate the logic for your routes from your endpoint definitions.
-obsolete: true
---
To keep your code organized, it is highly recommended to use **controller** or **action** classes. By separating your code in this way, you can easily see a list of the endpoints that a Sprinkle defines by looking at its route definitions. The implementation can then be tucked away in separate files.
@@ -114,9 +113,9 @@ class OwlController
public function __construct(
protected Twig $view,
protected VoleFinder $voleFinder)
- {
+ {
}
-
+
public function getOwls(string $genus, Request $request, Response $response): Response
{
// ...
diff --git a/app/pages/6.0/08.routes-and-controllers/05.registering-routes/docs.md b/app/pages/6.0/08.routes-and-controllers/05.registering-routes/docs.md
index 570f4f25..330cd5df 100644
--- a/app/pages/6.0/08.routes-and-controllers/05.registering-routes/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/05.registering-routes/docs.md
@@ -1,15 +1,14 @@
---
title: Registering Routes
description: Once your routes definitions are ready, you have to register them inside your Sprinkle Recipe.
-obsolete: true
---
-So far we've seen how to [create route definitions](routes-and-controllers/front-controller) and [controller classes](routes-and-controllers/controller-classes). However, there one last step required for our routes to be enabled inside our application. That is registering the route class inside the [Sprinkle Recipe](sprinkles/recipe#routes).
+So far we've seen how to [create route definitions](routes-and-controllers/front-controller) and [controller classes](routes-and-controllers/controller-classes). However, there one last step required for our routes to be enabled inside our application. That is registering the route class inside the [Sprinkle Recipe](sprinkles/recipe#routes).
> [!NOTE]
> Previous versions of UserFrosting relied on a naming convention for registering routes. Routes were expected to be placed in a special directory, and would automatically be registered at runtime. To provide more flexibility, the naming convention has been dropped in UserFrosting 5. You now have to register every class you wish to register, in the order you want them to be registered, inside the Sprinkle Recipe.
-The first step is to create a new class that will return the Slim route definition. This class **must** implement the `UserFrosting\Routes\RouteDefinitionInterface` interface from the UserFrosting Framework. For example :
+The first step is to create a new class that will return the Slim route definition. This class **must** implement the `UserFrosting\Routes\RouteDefinitionInterface` interface from the UserFrosting Framework. For example :
**app/src/MyRoutes.php**
```php
@@ -30,7 +29,7 @@ class MyRoutes implements RouteDefinitionInterface
}
```
-Note in the previous example how the class has a [FQN](https://www.php.net/manual/en/language.namespaces.rules.php) of `\UserFrosting\App\MyRoutes`.
+Note in the previous example how the class has a [FQN](https://www.php.net/manual/en/language.namespaces.rules.php) of `\UserFrosting\App\MyRoutes`.
To register this class inside your application, you need to add it to the `getRoutes()` method of the Sprinkle Recipe. Don't forget to add the previous class to the `use` block at the top. For example :
@@ -46,8 +45,8 @@ use UserFrosting\App\MyRoutes; // <-- Add here !
// ...
class MyApp implements SprinkleRecipe {
-
- // ...
+
+ // ...
/**
* Returns a list of routes definition in PHP files.
diff --git a/app/pages/6.0/08.routes-and-controllers/06.client-input/01.validation/docs.md b/app/pages/6.0/08.routes-and-controllers/06.client-input/01.validation/docs.md
index 9e2565ab..fecdcb8f 100644
--- a/app/pages/6.0/08.routes-and-controllers/06.client-input/01.validation/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/06.client-input/01.validation/docs.md
@@ -1,7 +1,6 @@
---
title: Validation
description: Client- and server-side validation are unified into one convenient interface using UserFrosting's Fortress package and a common set of rules defined in a JSON schema file.
-obsolete: true
---
The number one security rule in web development is: **never trust client input!**
diff --git a/app/pages/6.0/08.routes-and-controllers/06.client-input/02.csrf-guard/docs.md b/app/pages/6.0/08.routes-and-controllers/06.client-input/02.csrf-guard/docs.md
index a427ec08..870f0753 100644
--- a/app/pages/6.0/08.routes-and-controllers/06.client-input/02.csrf-guard/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/06.client-input/02.csrf-guard/docs.md
@@ -1,7 +1,6 @@
---
title: CSRF Protection
description: Cross-site request forgeries (CSRF) are a type of social engineering attack in which a malicious agent tricks a user into submitting a valid, but unintended request to your server. UserFrosting mitigates this risk with a secret token embedded into all forms on your website.
-obsolete: true
---
Cross-site request forgeries (CSRF) are a type of social engineering attack in which a malicious agent tricks a user into submitting a valid, but unintended request to your server. This can happen, for example, when a user opens a malicious email or website while they are still signed in to your website.
diff --git a/app/pages/6.0/08.routes-and-controllers/06.client-input/03.throttle/docs.md b/app/pages/6.0/08.routes-and-controllers/06.client-input/03.throttle/docs.md
index b54433ed..9a15991a 100644
--- a/app/pages/6.0/08.routes-and-controllers/06.client-input/03.throttle/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/06.client-input/03.throttle/docs.md
@@ -1,7 +1,6 @@
---
title: Throttling
description: Throttling, also known as rate-limiting, is a technique for slowing down attackers by limiting the frequency with which they can make certain types of requests.
-obsolete: true
---
People tend to be bad at picking strong passwords. [Publicly available lists of passwords](https://github.com/danielmiessler/SecLists/tree/master/Passwords) recovered from hacked databases reveal that, despite efforts to educate, users still pick the same highly predictable passwords over and over. These lists make it easy for brute-force attackers to gain unauthorized access to a large number of your users' accounts.
@@ -55,7 +54,7 @@ This process is generally implemented using the `getDelay` and `logEvent` method
// Inject Throttler into the class
public function __construct(
- // ...
+ // ...
protected Throttler $throttler,
// ...
) {
@@ -85,12 +84,12 @@ $this->db->transaction(function () use ($data) {
$this->throttler->logEvent('password_reset_request', [
'email' => $data['email'],
]);
-
+
// ...
});
-// ...
+// ...
```
You'll notice that we first check the `password_reset_request` throttle (the client IP address is automatically retrieved by the `throttler` service) and return an error if the computed delay is greater than 0. We do this *before* the call to `logEvent` - which adds a record of this attempt to the database - so that requests which are rejected because of the throttle rule do not further exacerbate the timeout period.
diff --git a/app/pages/6.0/08.routes-and-controllers/06.client-input/04.ajax/docs.md b/app/pages/6.0/08.routes-and-controllers/06.client-input/04.ajax/docs.md
index 5703bd96..4ddbaab9 100644
--- a/app/pages/6.0/08.routes-and-controllers/06.client-input/04.ajax/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/06.client-input/04.ajax/docs.md
@@ -1,6 +1,81 @@
---
title: AJAX Requests
-obsolete: true
+description: Learn how to handle AJAX requests in UserFrosting using JSON responses and RESTful endpoints.
---
-To contribute to this documentation, please submit a pull request to our [learn repository](https://github.com/userfrosting/learn/tree/master/pages).
\ No newline at end of file
+AJAX (Asynchronous JavaScript and XML) requests allow you to send and receive data from the server without refreshing the page. UserFrosting makes it easy to handle AJAX requests using standard HTTP methods and JSON responses.
+
+## Making AJAX Requests
+
+There are many ways to make AJAX requests from the frontend, including:
+
+- Modern **Fetch API** (built into browsers)
+- **Axios** (popular promise-based HTTP client)
+- jQuery's `$.ajax()` (if you're using jQuery)
+- Any other HTTP client library
+
+UserFrosting's frontend uses **Axios** for AJAX requests.
+
+## Handling AJAX Requests in Controllers
+
+Controllers handling AJAX requests should return JSON responses.
+
+### Example: A Simple AJAX Endpoint
+
+```php
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use UserFrosting\Sprinkle\Core\Exceptions\NotFoundException;
+
+class UserController
+{
+ public function getUser(Request $request, Response $response, array $args): Response
+ {
+ $userId = $args['id'];
+
+ // Fetch user from database
+ $user = User::find($userId);
+
+ // Throw exception for not found - will be converted to JSON error automatically
+ if (!$user) {
+ throw new NotFoundException('User not found');
+ }
+
+ // Prepare response data
+ $payload = [
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'email' => $user->email
+ ];
+
+ // Encode and write to response body
+ $json = json_encode($payload, JSON_THROW_ON_ERROR);
+ $response->getBody()->write($json);
+
+ return $response->withHeader('Content-Type', 'application/json');
+ }
+}
+```
+
+### Example: Frontend Example with Fetch API
+
+```javascript
+// GET request
+fetch('/api/users/123')
+ .then(response => response.json())
+ .then(data => {
+ console.log('User:', data);
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ });
+```
+
+## Best Practices
+
+1. **Always validate input** - Use [request validation](/routes-and-controllers/client-input/validation) for all incoming data
+2. **Include CSRF tokens** - Protect POST/PUT/DELETE requests with [CSRF guards](/routes-and-controllers/client-input/csrf-guard)
+3. **Return consistent JSON** - Use a standard structure for success and error responses
+4. **Use proper HTTP status codes** - 200 for success, 404 for not found, 422 for validation errors, etc.
+5. **Use exceptions for errors** - Throw appropriate exceptions (e.g., `NotFoundException`, `ValidationException`) which will be automatically converted to JSON error responses with proper status codes
+6. **Handle errors gracefully** - Always provide meaningful error messages in exception descriptions
diff --git a/app/pages/6.0/08.routes-and-controllers/06.client-input/docs.md b/app/pages/6.0/08.routes-and-controllers/06.client-input/docs.md
index 51deb2bf..5226c59f 100644
--- a/app/pages/6.0/08.routes-and-controllers/06.client-input/docs.md
+++ b/app/pages/6.0/08.routes-and-controllers/06.client-input/docs.md
@@ -1,7 +1,6 @@
---
title: Client Input
description: Retrieving client input (GET, POST, PUT, DELETE, and URL arguments) in your controllers.
-obsolete: true
---
There is no such thing as a `$_GET` array or a `$_POST` array - at least, not according to the [HTTP specifications](https://en.wikipedia.org/wiki/HTTP#Message_Format). These superglobals are merely constructs offered by PHP to make your life more "convenient".
diff --git a/app/pages/6.0/08.routes-and-controllers/chapter.md b/app/pages/6.0/08.routes-and-controllers/chapter.md
index 3509fa12..03566e9e 100644
--- a/app/pages/6.0/08.routes-and-controllers/chapter.md
+++ b/app/pages/6.0/08.routes-and-controllers/chapter.md
@@ -1,13 +1,14 @@
---
title: Routes and Controllers
description: UserFrosting controllers are used to mediate interactions between the model and view, and are responsible for much of your application's logic.
-obsolete: true
---
#### Chapter 8
# Routes and Controllers
-UserFrosting controllers are used to mediate interactions between the model and view, and are responsible for much of your application's logic.
+Every web application needs to answer a fundamental question: **when a user visits a URL or submits a form, what code should run?** Without a structured approach, you end up with spaghetti code mixing database queries, business logic, and HTML generation in confusing, unmaintainable ways.
-This chapter explains RESTful design, the front controller pattern, creating new controllers and controller methods to implement the logic for your application, and how to properly handle input from the client.
+UserFrosting uses the **MVC (Model-View-Controller)** pattern with RESTful routing to cleanly separate concerns. **Routes** map URLs to **controllers**, which contain your application logic and coordinate between **models** (data) and **views** (presentation). This separation makes your code easier to understand, test, and maintain.
+
+This chapter covers RESTful API design, the front controller pattern, creating controllers for your features, and securely handling user input. You'll learn to build well-structured endpoints that are both powerful and maintainable.
diff --git a/app/pages/6.0/09.configuration/01.environment-vars/docs.md b/app/pages/6.0/09.configuration/01.environment-vars/docs.md
index 3f3af36e..bfd22a5c 100644
--- a/app/pages/6.0/09.configuration/01.environment-vars/docs.md
+++ b/app/pages/6.0/09.configuration/01.environment-vars/docs.md
@@ -1,10 +1,18 @@
---
title: Environment Variables
description: The .env file is used to define important values in development such as database credentials, which should be placed directly in environment variables during production.
-obsolete: true
+wip: true
---
-The basic database settings for UserFrosting can be set through environment variables. By default, UserFrosting looks for the following environment variables:
+Every application needs configuration—database credentials, API keys, SMTP settings, feature flags. But hardcoding these values directly in your code creates serious problems: security vulnerabilities (credentials in version control), inflexibility (can't easily change settings), and deployment headaches (different environments need different values).
+
+UserFrosting uses **environment variables** to solve these challenges. Environment variables keep sensitive data out of your codebase, allow different configuration for each environment (development, staging, production), and let you change settings without modifying code. It's the [twelve-factor app](https://12factor.net/config) approach that professional applications follow.
+
+For local development, UserFrosting uses `.env` files to make managing environment variables easy. In production, you set real environment variables directly on your server for maximum security.
+
+## Available Environment Variables
+
+UserFrosting recognizes the following environment variables:
| Variable | Description |
|:--------------------:|-----------------------------------------------------------------------------------------|
diff --git a/app/pages/6.0/09.configuration/02.config-files/docs.md b/app/pages/6.0/09.configuration/02.config-files/docs.md
index 3a4c493c..16bb6aa9 100644
--- a/app/pages/6.0/09.configuration/02.config-files/docs.md
+++ b/app/pages/6.0/09.configuration/02.config-files/docs.md
@@ -1,7 +1,7 @@
---
title: Configuration Files
description: Configuration files allow you to customize the default behavior of UserFrosting - for example, to toggle debugging, caching, and logging behaviors and to set other sitewide settings.
-obsolete: true
+wip: true
---
Configuration files allow you to customize the default behavior of UserFrosting - for example, to toggle debugging, caching, and logging behaviors and to set other sitewide settings. Configuration files are found in the `config/` directory of each Sprinkle.
diff --git a/app/pages/6.0/09.configuration/chapter.md b/app/pages/6.0/09.configuration/chapter.md
index 7338a6af..d522b72e 100644
--- a/app/pages/6.0/09.configuration/chapter.md
+++ b/app/pages/6.0/09.configuration/chapter.md
@@ -1,7 +1,7 @@
---
title: Configuration
description: UserFrosting works out of the box with very little configuration required. That being said, UserFrosting is highly configurable for the needs of your specific application.
-obsolete: true
+wip: true
---
#### Chapter 9
diff --git a/app/pages/6.0/10.users/01.user-accounts/docs.md b/app/pages/6.0/10.users/01.user-accounts/docs.md
index 5aca12e9..d3c008aa 100644
--- a/app/pages/6.0/10.users/01.user-accounts/docs.md
+++ b/app/pages/6.0/10.users/01.user-accounts/docs.md
@@ -1,7 +1,7 @@
---
title: User Accounts
description: UserFrosting ships with everything you need to create user accounts, and a rich set of features for users and administrators.
-obsolete: true
+wip: true
---
You were probably attracted to UserFrosting because you wanted to "make a site where users can sign in", or you already have a project in progress and your boss asked you to "put it behind a login," or you need to have some "protected pages." These are nontechnical terms. It will be easier for us to communicate if we first establish a common vocabulary, so that we can explain the concepts with more precision.
diff --git a/app/pages/6.0/10.users/02.access-control/docs.md b/app/pages/6.0/10.users/02.access-control/docs.md
index 25aef952..e44348ac 100644
--- a/app/pages/6.0/10.users/02.access-control/docs.md
+++ b/app/pages/6.0/10.users/02.access-control/docs.md
@@ -1,9 +1,15 @@
---
title: Authorization
description: Authorization is sometimes referred to as "access control" or "protecting pages". UserFrosting implements an extended version of role-based access control that supports procedural conditions on user permissions.
-obsolete: true
+wip: true
---
+Authenticating users (knowing who they are) is only half the battle. The harder question is: **what should each user be allowed to do?** Not every user should access administrative features, delete data, or view sensitive information. Without proper authorization, your application becomes either a security hole (everyone can do everything) or a maintenance nightmare (hardcoded permissions scattered everywhere).
+
+UserFrosting solves this with a powerful, flexible **role-based access control (RBAC)** system. But it goes beyond simple RBAC—UserFrosting adds **conditional permissions** that let you define rules like "users can edit their own posts" or "managers can approve requests in their department." This gives you fine-grained control without drowning in complexity.
+
+This page explains how roles and permissions work, how to perform access checks in your code, and how to create sophisticated permission rules that adapt to your application's needs.
+
## Roles
UserFrosting implements an extended version of [role-based access control](https://en.wikipedia.org/wiki/Role-based_access_control), which allows for very fine-grained control over user permissions. Every user can have zero or more **roles**, and every role can have zero or more **permissions**. Users' effective permissions are determined through their roles.
@@ -126,7 +132,7 @@ UserFrosting ships with a number of predefined access condition callbacks, which
### Custom callbacks
-To add your own access condition callbacks, simply extend `UserFrosting\Sprinkle\Account\Authorize\AccessConditions` and replace it in a custom Service Provider. For example :
+To add your own access condition callbacks, simply extend `UserFrosting\Sprinkle\Account\Authorize\AccessConditions` and replace it in a custom Service Provider. For example :
```php
use UserFrosting\ServicesProvider\ServicesProviderInterface;
@@ -146,7 +152,7 @@ final class CustomAccessConditionsService implements ServicesProviderInterface
}
```
-
{% endblock %}
```
-Twig will automatically convert the array you passed to `render` in your page controller method to a JSON object:
-
-```js
-
-// Appears in the rendered page DOM for /account/register
-
-var page = {
- "validators": {
- "register": {
- "rules": {
- "user_name": {
- "rangelength": [
- 1,
- 50
- ],
- "noLeadingWhitespace": true,
- "noTrailingWhitespace": true,
- "required": true,
- "username": true
- },
- ...
- },
- "messages": {
- "user_name": {
- "rangelength": "Username must be between 1 and 50 characters in length.",
- "noLeadingWhitespace": "The value for 'Username' cannot begin with spaces, tabs, or other whitespace.",
- "noTrailingWhitespace": "The value for 'Username' cannot end with spaces, tabs, or other whitespace.",
- "required": "Please specify a value for 'Username'.",
- "username": "Username may consist only of lowercase letters, numbers, '.', '-', and '_'."
- },
- ...
- }
- }
- }
-};
+**JavaScript/TypeScript Access**:
+```typescript
+// The page object is now available globally
+console.log(page.api_endpoint) // "/api/users"
+console.log(page.per_page) // 25
+
+// Use it in your Vue components or vanilla JS
+const users = page.initial_data
```
-## Dynamically extending JSON objects
+This approach works well for validation rules, API endpoints, initial data loads, and feature flags specific to a page.
+
+### 3. Vue Component Props (Best for Component Data)
+
+When using Vue 3 components, pass data directly as props through the component's mounting HTML.
-Occasionally, you will want to dynamically modify the contents of `site`, `page`, or some other JSON variable. For example, you might want to override a variable on a specific page. To do this, you can use jQuery's `extend` method:
+**Twig Template**:
+```twig
+
+
+
+
+```
+
+Or, better yet, pass props when creating the Vue app:
+
+**Twig Template**:
+```twig
+
-```js
```
-You should do this in your page template's `scripts_page` block, after loading the original versions of the variables but before loading any page asset bundles.
+**TypeScript**:
+```typescript
+import { createApp } from 'vue'
+import UserProfile from './components/UserProfile.vue'
+
+createApp(UserProfile, {
+ userId: window.userProfileData.userId,
+ username: window.userProfileData.username,
+ email: window.userProfileData.email
+}).mount('#user-profile')
+```
+
+### 4. Data Attributes (Best for Small Amounts of Data)
+
+For simple, element-specific data, use HTML5 `data-*` attributes. This is ideal when your JavaScript needs to know about specific elements on the page.
+
+**Twig Template**:
+```twig
+
+```
+
+**TypeScript**:
+```typescript
+document.querySelectorAll('.delete-user').forEach(button => {
+ button.addEventListener('click', async (e) => {
+ const target = e.currentTarget as HTMLButtonElement
+ const userId = target.dataset.userId
+ const userName = target.dataset.userName
+
+ if (confirm(`Delete user ${userName}?`)) {
+ await deleteUser(userId)
+ }
+ })
+})
+```
+
+**Vue 3 Alternative** (using template refs):
+```vue
+
+
+
+
+
+```
+
+### 5. API Requests (Best for Dynamic Data)
+
+For data that changes frequently or is too large to embed, fetch it via API calls after the page loads.
+
+**TypeScript**:
+```typescript
+import axios from 'axios'
+
+interface User {
+ id: number
+ username: string
+ email: string
+}
+
+// Fetch data after page load
+async function loadUsers(): Promise {
+ const response = await axios.get(`${site.uri.public}/api/users`)
+ return response.data
+}
+
+// Use in your code
+const users = await loadUsers()
+```
+
+**Vue 3 Composable** (reusable logic):
+```typescript
+// composables/useUsers.ts
+import { ref, Ref } from 'vue'
+import axios from 'axios'
+
+export function useUsers() {
+ const users: Ref = ref([])
+ const loading = ref(false)
+ const error = ref(null)
+
+ async function fetchUsers() {
+ loading.value = true
+ error.value = null
+
+ try {
+ const response = await axios.get(`${site.uri.public}/api/users`)
+ users.value = response.data
+ } catch (e) {
+ error.value = e as Error
+ } finally {
+ loading.value = false
+ }
+ }
+
+ return { users, loading, error, fetchUsers }
+}
+```
+
+## Choosing the Right Approach
+
+Use this decision tree:
+
+| Data Type | Best Approach | Why |
+|-----------|---------------|-----|
+| Site-wide config (URLs, CSRF) | Global `site` object | Available everywhere, cached |
+| Page-specific settings | Global `page` object | Page scope, easy access |
+| Component initialization | Vue props | Type-safe, reactive |
+| Element metadata | Data attributes | Semantic, standard HTML |
+| Large/dynamic data | API requests | Reduces initial page size |
+| Real-time data | API + polling/WebSockets | Always current |
+
+## Modern ES Modules and TypeScript
+
+With Vite and modern JavaScript, avoid global variables when possible. Instead, export and import values:
+
+**config.ts**:
+```typescript
+// Re-export site config with types
+export interface SiteConfig {
+ uri: {
+ public: string
+ }
+ csrf: {
+ name: string
+ value: string
+ keys: {
+ name: string
+ value: string
+ }
+ }
+}
+
+// Access global site object with type safety
+export const siteConfig: SiteConfig = (window as any).site
+```
+
+**Usage**:
+```typescript
+import { siteConfig } from './config'
+
+// Now fully typed!
+const url = `${siteConfig.uri.public}/api/users`
+```
+
+## Security Considerations
+
+> [!WARNING]
+> **Never expose sensitive data to the client:**
+> - Database credentials
+> - API keys or secrets
+> - Other users' private information
+> - Internal system paths
+> - Unfiltered user input (XSS risk)
+
+**Safe to expose:**
+- Public URLs and endpoints
+- CSRF tokens (designed for client use)
+- Current user's own data
+- Public configuration settings
+
+**Remember**: Anything in the HTML can be viewed by the user. Treat all client-side data as potentially compromised.
+
+## Debugging Data Transfer
+
+### View Available Data
+
+Open your browser's console and type:
+```javascript
+console.log('Site config:', site)
+console.log('Page data:', page)
+```
+
+### Validate JSON Structure
+
+If data isn't working as expected, check that your PHP array converts correctly to JSON:
+
+```php
+// Good - simple types convert cleanly
+'count' => 42,
+'name' => 'John',
+'active' => true
+
+// Problematic - resource types don't serialize
+'database' => $pdo, // ❌ Won't work
+
+// Solution - extract only the data you need
+'user' => [
+ 'id' => $user->id,
+ 'name' => $user->username
+]
+```
+
+## What's Next?
+
+Now that you know how to pass data from server to client, learn how to use it in:
+
+- **[Vue Components](client-side-code/vue-components)**: Build reactive UIs with the data
+- **[Forms](client-side-code/components/forms)**: Submit data back to the server
+- **[Tables](client-side-code/components/tables)**: Display collections of data
+
+> [!TIP]
+> Start simple with global objects (`site`, `page`), then graduate to Vue props and API calls as your application grows in complexity.
diff --git a/app/pages/6.0/15.client-side-code/03.client-side-templating/docs.md b/app/pages/6.0/15.client-side-code/03.client-side-templating/docs.md
deleted file mode 100644
index 1848be09..00000000
--- a/app/pages/6.0/15.client-side-code/03.client-side-templating/docs.md
+++ /dev/null
@@ -1,186 +0,0 @@
----
-title: Client-side Templating
-description: An overview of how UserFrosting uses Handlebars.js for client-side templating.
-obsolete: true
----
-
-
-In [Templating with Twig](templating-with-twig), we learned how Twig helps you separate the logic of your server-side application from the layout of the pages it generates. Handlebars.js plays a similar role on the client side of your application, in Javascript. The main difference is that with Twig we are often generating complete pages, whereas with Handlebars we typical only generate smaller snippets of HTML to be inserted into the DOM.
-
-## Handlebars.js basic usage
-
-Handlebars is fairly straightforward to use. A Handlebars template is created by calling `Handlebars.compile` on a string:
-
-```js
-var counterTemplate = Handlebars.compile("Owls in my care: {{owls.count}}");
-```
-
-We can then render this template, calling the template on a JSON object and appending the rendered template to an element in our page's DOM:
-
-```js
-var counterRendered = counterTemplate({
- owls: {
- count: 5
- }
-});
-var counterDOM = $(counterRendered).appendTo($("#parentDiv"));
-```
-
-Of course, the JSON object is optional and can be omitted if your template does not contain any dynamically generated content.
-
-### Template blocks
-
-Since writing long HTML snippets as strings can become unwieldy and difficult to read, we can place our templates in `script` tags with the `text/x-handlebars-template` type attribute. The entire `script` block can then be rendered in our page's Twig template:
-
-```html
-
-{# This contains a series of
-{% endverbatim %}
-```
-
-By placing each Handlebars template in a separate `script` tag, and giving each one a unique `id`, we make it easy to choose a template to compile and render in our Javascript code:
-
-```js
-var owlTemplate = Handlebars.compile($("#owl-description-item").html());
-```
-
-Note that since Handlebars and Twig have similar syntax, we wrap our Handlebars templates in Twig's `verbatim` tag so that Twig won't try to parse the Handlebars template when it renders the page.
-
-## Template syntax
-
-Handlebars.js and Twig use a similar syntax. As with Twig, placeholders are represented using the `{{double-mustache}}` notation. However, compared to Twig, Handlebars.js is fairly sparse in terms of control structures and other features.
-
-### If blocks
-
-In Handlebars, `#if` is a **helper**. Helpers always begin with `#` in their opening tag, and `/` in their closing tag.
-
-```html
-
- {{#if author}}
-
{{firstName}} {{lastName}}
- {{else}}
-
Unknown Author
- {{/if}}
-
-```
-
-It's also important to note that the `if` helper in Handlebars doesn't support logical expressions. For example:
-
-```html
-
-{{#if author == "Attenborough"}}
-
{{firstName}} {{lastName}}
-{{/if}}
-```
-
-To compare two values in an if/else block, use our custom Handlebars helper instead:
-
-```html
-{{#ifx author '==' 'David Attenborough' }}
-
{{firstName}} {{lastName}}
-{{/ifx}}
-```
-
-> [!IMPORTANT]
-> `#ifx` supports the basic logical operators (`==`, `!=`, `>`, `<`, etc), but does not support compound expressions. You can instead nest your expressions, or create your own custom helper. For more information, see [this Stack Overflow question](http://stackoverflow.com/questions/8853396/logical-operator-in-a-handlebars-js-if-conditional).
-
-### Loops
-
-Handlebars.js provides a limited syntax for loops using the `#each` helper:
-
-If you pass this object:
-
-```js
-var data = {
- owls: [
- "Fluffers",
- "Slasher",
- "Muhammad Owli"
- ]
-}
-```
-
-...when rendering this template:
-
-```html
-
- {{#each owls}}
-
{{this}}
- {{/each}}
-
-```
-
-Handlebars will generate the `li`s with the values from the `owls` list. In an `#each` block, `this` refers to the current value in our iteration over the array.
-
-In the real world, it's actually **fairly rare** that you will end up using the `#each` helper. More likely, you will end up with a template that renders just a **single element** of a list or row in a table. You'll then render each row using a jQuery loop:
-
-**Template:**
-
-```html
-{# "Parent" element that will hold your list of owls #}
-
-
-
-{# Handlebars template for generating the list items in myOwls #}
-{% verbatim %}
-
-{% endverbatim %}
-```
-
-Your page Javascript might have something like:
-
-```js
-// Compile the template
-var owlItemTemplate = Handlebars.compile($("#owls-list-item").html());
-
-// Loop through the owls, rending each item individually and then insert in the parent element
-$.each(data.owls, function (idx, owl) {
- var owlItemRendered = owlItemTemplate(owl);
- $(owlItemRendered).appendTo($("#myOwls"));
-});
-```
-
-This approach is very common in client side templating, because users often need to dynamically interact with collections of elements. Having a template for a single row or item allows you to dynamically add and remove individual items in your client-side application.
-
-## Other built-in helpers
-
-Handlebars' full list of built-in helpers can be found [here](http://handlebarsjs.com/guide/builtin-helpers.html#if).
-
-## Extra helpers
-
-UserFrosting provides some additional helpers on top of Handlebars' built-in ones:
-
-### dateFormat
-
-Format an ISO date using [Moment.js](http://momentjs.com).
-
-```
-{{name}} was adopted on {{dateFormat adoption_date format="MMMM YYYY"}}.
-```
-
-`format` should be a valid Moment.js [format string](https://momentjs.com/docs/#/displaying/format/).
-
-### phoneUSFormat
-
-Format a string as a US phone number (`(xxx) xxx-xxxx`)
-
-### currencyUsdFormat
-
-Format a floating-point value as US currency.
diff --git a/app/pages/6.0/15.client-side-code/03.vue-components/docs.md b/app/pages/6.0/15.client-side-code/03.vue-components/docs.md
new file mode 100644
index 00000000..5f009eb6
--- /dev/null
+++ b/app/pages/6.0/15.client-side-code/03.vue-components/docs.md
@@ -0,0 +1,895 @@
+---
+title: Building Vue 3 Components
+description: Learn how to create reactive, reusable UI components with Vue 3 Single File Components
+wip: true
+---
+
+Vue 3 is UserFrosting's recommended approach for building interactive user interfaces. Unlike the imperative jQuery patterns of the past, Vue uses declarative, component-based architecture that makes your code easier to understand, test, and maintain.
+
+This guide introduces Vue 3 components and shows you how to use them in UserFrosting applications.
+
+## What Are Vue Components?
+
+A Vue component is a self-contained piece of UI with its own:
+- **Template** (HTML structure)
+- **Logic** (JavaScript/TypeScript behavior)
+- **Styles** (CSS, scoped to the component)
+
+Think of components as custom HTML elements you can reuse throughout your application.
+
+**Simple example**:
+```vue
+
+
+
+
+
+
+
+```
+
+This single file defines everything the component needs. When used in your application, it creates an interactive button that tracks how many times it's been clicked.
+
+## Single File Components (SFCs)
+
+Vue 3 components in UserFrosting are written as **Single File Components** with a `.vue` extension. Each file contains everything the component needs in one place.
+
+**File structure**:
+```
+app/assets/
+└── components/
+ ├── UserCard.vue
+ ├── UserForm.vue
+ └── shared/
+ ├── Button.vue
+ └── Modal.vue
+```
+
+### Anatomy of an SFC
+
+**UserCard.vue** - A complete component example:
+```vue
+
+
+
+
+
{{ username }}
+
{{ email }}
+
+
+
+
+
+
+
+
+
+```
+
+The beauty of SFCs is that everything related to the component lives in one file, making it easy to understand and maintain.
+
+## Composition API with Script Setup
+
+UserFrosting uses Vue 3's `
+
+
+
+
+
{{ greeting }}
+
Age: {{ user.age }}
+
+
+
+```
+
+**Why we prefer `
+
+
+
{{ fullName }}
+
+
+```
+
+## Template Syntax
+
+Vue templates look like HTML but with special directives for dynamic behavior.
+
+### Text Interpolation
+
+Display reactive data with double curly braces:
+
+```vue
+
+
Message: {{ message }}
+
Count + 1: {{ count + 1 }}
+
Uppercase: {{ message.toUpperCase() }}
+
{{ isActive ? 'Active' : 'Inactive' }}
+
+```
+
+### Attribute Binding (v-bind / :)
+
+Bind reactive data to HTML attributes:
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+**v-show** - Elements stay in DOM, just toggle `display`:
+
+```vue
+
+
+
+ This toggles display: none
+
+
+```
+
+### List Rendering (v-for)
+
+Loop through arrays or objects:
+
+```vue
+
+
+
+
+ {{ user.name }}
+
+
+
+
+
+
+ {{ index + 1 }}. {{ user.name }}
+
+
+
+
+
+
+ {{ key }}: {{ value }}
+
+
+
+```
+
+> [!IMPORTANT]
+> Always provide a unique `:key` when using `v-for`. This helps Vue track elements efficiently.
+
+### Two-Way Binding (v-model)
+
+Synchronize form inputs with state:
+
+```vue
+
+
+
+
+
+
+
+```
+
+### Emits (Child → Parent)
+
+Send events from child to parent:
+
+**Child.vue**:
+```vue
+
+
+
+
+
+```
+
+**Parent.vue**:
+```vue
+
+
+
+
+
+```
+
+## Lifecycle Hooks
+
+Run code at specific points in a component's life:
+
+```vue
+
+```
+
+**Most commonly used**:
+- `onMounted` - Fetch data, initialize libraries
+- `onUnmounted` - Cleanup (timers, listeners, subscriptions)
+
+## Using Components in UserFrosting
+
+### Step 1: Create the Component
+
+**app/assets/components/UserGreeting.vue**:
+```vue
+
+
+
Hello, {{ username }}!
+
You have {{ messageCount }} new messages
+
+
+
+
+
+
+
+```
+
+### Step 2: Register in Entry Point
+
+**app/assets/main.ts**:
+```typescript
+import { createApp } from 'vue'
+import UserGreeting from './components/UserGreeting.vue'
+
+const app = createApp({})
+
+// Register component globally
+app.component('UserGreeting', UserGreeting)
+
+// Mount to DOM
+app.mount('#app')
+```
+
+### Step 3: Use in Twig Template
+
+```twig
+{# Make sure you have a mount point #}
+
+
+```
+
+## TypeScript Best Practices
+
+UserFrosting components use TypeScript for better developer experience:
+
+```vue
+
+```
+
+## Best Practices
+
+### 1. Keep Components Focused
+
+Each component should have a single, clear purpose:
+
+✅ **Good**:
+- `UserCard.vue` - Display user info
+- `UserForm.vue` - Edit user
+- `UserList.vue` - List users
+
+❌ **Bad**:
+- `UserEverything.vue` - Does all user-related things
+
+### 2. Use Scoped Styles
+
+Always scope styles to avoid global conflicts:
+
+```vue
+
+```
+
+### 3. Props Down, Events Up
+
+Follow unidirectional data flow:
+- Parent passes data to child via props
+- Child notifies parent via events
+- Never mutate props directly
+
+```vue
+
+
+
+
+
+```
+
+### 4. Extract Reusable Logic
+
+Move shared logic to composables:
+
+```typescript
+// composables/useFetch.ts
+export function useFetch(url: string) {
+ const data = ref(null)
+ const loading = ref(false)
+ const error = ref(null)
+
+ async function fetch() {
+ loading.value = true
+ try {
+ const response = await axios.get(url)
+ data.value = response.data
+ } catch (e) {
+ error.value = e as Error
+ } finally {
+ loading.value = false
+ }
+ }
+
+ return { data, loading, error, fetch }
+}
+```
+
+## Debugging Vue Components
+
+### Vue DevTools
+
+Install [Vue DevTools](https://devtools.vuejs.org/) browser extension to:
+- Inspect component tree
+- View component state and props
+- Track emitted events
+- Profile component performance
+- Time-travel debug (see state history)
+
+### Console Debugging
+
+```vue
+
+
+
+
+
{{ JSON.stringify(user, null, 2) }}
+
+```
+
+## What's Next?
+
+Now that you understand Vue 3 components, learn how to build specific UI patterns:
+
+- **[Forms](client-side-code/components/forms)**: Build validated, AJAX-powered forms
+- **[Tables](client-side-code/components/tables)**: Create sortable, filterable data tables
+- **[Alerts](client-side-code/components/alerts)**: Display notifications and messages
+- **[Collections](client-side-code/components/collections)**: Manage dynamic lists
+
+## Further Learning
+
+- **[Vue 3 Official Guide](https://vuejs.org/guide/)** - Comprehensive Vue documentation
+- **[TypeScript with Vue](https://vuejs.org/guide/typescript/overview.html)** - TypeScript integration
+- **[Vite Documentation](https://vitejs.dev/guide/)** - Build tool details
+- **[Composition API FAQ](https://vuejs.org/guide/extras/composition-api-faq.html)** - Why Composition API?
+
+> [!TIP]
+> Don't try to learn everything at once. Start with basic components, add reactivity, then gradually explore advanced features like composables and TypeScript as you need them.
diff --git a/app/pages/6.0/15.client-side-code/04.components/01.forms/docs.md b/app/pages/6.0/15.client-side-code/04.components/01.forms/docs.md
index 9bb0df26..f9e6eec8 100644
--- a/app/pages/6.0/15.client-side-code/04.components/01.forms/docs.md
+++ b/app/pages/6.0/15.client-side-code/04.components/01.forms/docs.md
@@ -1,166 +1,706 @@
---
-title: Forms
-description: The ufForm widget makes it easy to set up simple forms for validation and AJAX submission.
-obsolete: true
+title: Building Forms with Vue 3
+description: Create validated, AJAX-powered forms using Vue 3 and modern form handling patterns
+wip: true
---
-You may have noticed that in UserFrosting, forms are usually submitted via an AJAX request. By submitting forms with AJAX rather than HTML's native form submission, we can control the behavior of the page before submission (client-side validation, transforming form data) and after submission (deciding whether to reload the page, redirect, display messages, etc).
-
-UserFrosting's `ufForm` widget makes it easy to handle many form-submission tasks automatically. Simply create your usual form markup:
+Forms are essential for user interaction in web applications. UserFrosting 6.0 uses Vue 3 to create reactive, validated forms that submit via AJAX, providing a smooth user experience without full page reloads.
+
+This guide shows you how to build forms with Vue 3, handle validation, submit data to your API, and display feedback to users.
+
+## Basic Form Component
+
+Here's a simple login form component to get started:
+
+**LoginForm.vue**:
+```vue
+
+