diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c9f8a323..954beb03 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -73,14 +73,14 @@ 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: `![Alt text](/images/screenshot.png)` -- ❌ Wrong: `![Alt text](images/screenshot.png)` or `![Alt text](../images/screenshot.png)` +**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: `![Alt text](images/screenshot.png)` +- ❌ Wrong: `![Alt text](/images/screenshot.png)` or `![Alt text](../images/screenshot.png)` 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. 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): -![BlooP and FlooP and GlooP](/images/theoretical_mathematics_however_never_goes_out_of_fashion.png) +![BlooP and FlooP and GlooP](images/theoretical_mathematics_however_never_goes_out_of_fashion.png) 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..eca8dbc4 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,7 +1,7 @@ --- 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 +wip: 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. 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..e0c65e5b 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,7 @@ --- title: Develop Locally, Serve Globally description: The right way to approach development. -obsolete: true +wip: true --- Just about every week, we see someone wander into [chat](https://chat.userfrosting.com) and ask: @@ -28,7 +28,7 @@ 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! 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..78aa0823 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,7 @@ --- 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 +wip: 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..9355f65b 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,7 @@ --- 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 +wip: 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/docs.md b/app/pages/6.0/02.background/05.security/docs.md index 99e5e09f..4f9b5470 100644 --- a/app/pages/6.0/02.background/05.security/docs.md +++ b/app/pages/6.0/02.background/05.security/docs.md @@ -1,7 +1,7 @@ --- 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 +wip: true --- It is essential to understand some basic security concepts before diving into web development. 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..1e4eb127 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,7 @@ --- 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 +wip: 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. 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..d01ae3a9 100644 --- a/app/pages/6.0/03.structure/01.introduction/docs.md +++ b/app/pages/6.0/03.structure/01.introduction/docs.md @@ -1,7 +1,7 @@ --- title: Your Application description: A Sprinkle can contain assets, configuration files, translations, routes, PHP classes, and Twig templates. -obsolete: true +wip: 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! @@ -14,7 +14,7 @@ To make things easier, UserFrosting 5 now separates all of your code from UserFr ## 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 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..aed3fe5d 100644 --- a/app/pages/6.0/03.structure/02.dependencies/docs.md +++ b/app/pages/6.0/03.structure/02.dependencies/docs.md @@ -1,15 +1,15 @@ --- title: Built on the shoulders of giants description: Detailed breakdown of a UserFrosting's dependencies. -obsolete: true +wip: 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. @@ -28,7 +28,7 @@ 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. 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..447a7724 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,7 @@ --- title: The Framework description: A simple description of the UserFrosting Framework. -obsolete: true +wip: 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,7 +11,7 @@ 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 : +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. 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..84756c56 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,7 @@ --- title: Sprinkles, what are they? description: Detailed breakdown of a sprinkle's contents. -obsolete: true +wip: 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 +14,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. diff --git a/app/pages/6.0/03.structure/chapter.md b/app/pages/6.0/03.structure/chapter.md index 3b4e8c38..ddf5a779 100644 --- a/app/pages/6.0/03.structure/chapter.md +++ b/app/pages/6.0/03.structure/chapter.md @@ -1,7 +1,7 @@ --- title: App Structure description: UserFrosting is modular application framework built on the shoulders of giants. This chapter describes how UserFrosting is structured. -obsolete: true +wip: 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..7510d589 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,7 @@ --- title: Basic Stack Requirements description: UserFrosting requires a web server, PHP, and some sort of database. -obsolete: true +wip: 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 +20,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) @@ -49,7 +49,7 @@ As for your local development environment ([You _do_ have a local development en ### 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..5f098d5e 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,7 @@ --- 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 +wip: 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). @@ -47,7 +47,7 @@ Composer also handles autoloading, which means that the days of needing long blo ## 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. @@ -59,7 +59,7 @@ Although UserFrosting does not _run_ on Node.js, it does use several Node-based ## 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. 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..79b98eba 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,9 @@ --- 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 +wip: 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..bbce6f70 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,17 @@ --- title: Installing Requirements description: Getting UserFrosting up and running in your development environment. -obsolete: true +wip: 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) @@ -26,27 +34,27 @@ If you followed the previous pages, you probably noticed two pieces of software ### 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.3 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.3, from the terminal : `brew install shivammathur/php/php@8.3` > [!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 ``` @@ -157,7 +165,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 +176,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 +193,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..8f3062a2 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,7 @@ --- title: Installing UserFrosting description: Getting UserFrosting up and running in your development environment. -obsolete: true +wip: 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 ! 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..b1f4c2cc 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,11 @@ --- title: Native Installation description: Getting UserFrosting up and running in your development environment. -obsolete: true +wip: 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..61e68c9d 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,7 @@ --- title: Docker description: Docker is a containerization platform that helps maintain consistent behavior across different development and production environments. -obsolete: true +wip: true --- If you don't already have a local development environment set up, this page will guide you through installing UserFrosting using Docker. @@ -21,7 +21,7 @@ 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: @@ -34,10 +34,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 +46,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 +71,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. -![Basic front page of a UserFrosting installation](/images/front-page.png) +![Basic front page of a UserFrosting installation](images/front-page.png) To stop the containers, run: @@ -96,7 +96,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..9e7c9dd4 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,7 @@ --- title: Dev Environment description: Getting UserFrosting up and running in your development environment. -obsolete: true +wip: 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 index ad4e6ad1..ed3bbafd 100644 --- a/app/pages/6.0/04.installation/_modular/cli/docs.md +++ b/app/pages/6.0/04.installation/_modular/cli/docs.md @@ -1,22 +1,22 @@ -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 : +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 +#### 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! +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 : +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. +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..22c2da3d 100644 --- a/app/pages/6.0/04.installation/chapter.md +++ b/app/pages/6.0/04.installation/chapter.md @@ -1,11 +1,15 @@ --- 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 +wip: 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..b7f73fa1 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,7 @@ --- 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 +wip: 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. 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..f5b1c43c 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,7 @@ --- 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 +wip: 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..c825e751 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,7 +1,7 @@ --- title: Common Problems description: Commonly encountered issues when setting up, developing, or deploying a UserFrosting project. -obsolete: true +wip: true --- ## Installation diff --git a/app/pages/6.0/05.troubleshooting/chapter.md b/app/pages/6.0/05.troubleshooting/chapter.md index 9d52fc65..9f1d9db3 100644 --- a/app/pages/6.0/05.troubleshooting/chapter.md +++ b/app/pages/6.0/05.troubleshooting/chapter.md @@ -1,7 +1,7 @@ --- title: Troubleshooting description: Troubleshooting installation and your application. -obsolete: true +wip: 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..9cb84afc 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,14 @@ --- title: Basic concept -description: "" -obsolete: true +description: Learn how sprinkles provide a modular system for extending UserFrosting without modifying core code. +wip: true --- -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. +Here's a problem every developer faces: **how do you customize a framework without creating a maintenance nightmare?** If you modify framework files directly, updates become treacherous—merge conflicts, lost changes, and the constant fear of breaking things. Your customizations become entangled with core code, making upgrades nearly impossible. -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. +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. + +**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 +29,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..84b4a664 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,17 @@ --- 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. +wip: true --- +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 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..196d4af1 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,23 @@ --- 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. +wip: true --- -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. ## 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 +26,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 @@ -86,7 +88,7 @@ public function getSprinkles(): array } ``` -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`, `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`: ```php public function getSprinkles(): array { @@ -111,7 +113,7 @@ 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; 2. Account is then checked. It depends on Core, which is already loaded, so **Account** is the second loaded sprinkle; @@ -126,7 +128,7 @@ Because of sprinkle dependencies, in all three examples the order will be `Core ### 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 +144,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 +188,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 +220,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 +254,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 +272,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 +289,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 +316,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:** @@ -342,7 +344,7 @@ 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 + ```php public function getSprinkles(): array { return [ @@ -369,9 +371,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 +395,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..5811a035 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,7 @@ --- title: Customizing Your Sprinkle description: This guide walks you though the process of setting up your application by implementing a new sprinkle. -obsolete: true +wip: 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! 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..4a9163df 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,7 @@ --- title: Community Sprinkles description: Sprinkles shared between projects are called community sprinkles. -obsolete: true +wip: 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**. @@ -40,7 +40,7 @@ use Owlfancy\Sprinkle\Owlery ; //don't forget to include the sprinkle's namespac > If the sprinkle does not include Javascript, 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)... +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)... ```json "dependencies": { "@userfrosting/sprinkle-admin": "~5.1.0", @@ -70,12 +70,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 +88,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..498b2939 100644 --- a/app/pages/6.0/06.sprinkles/chapter.md +++ b/app/pages/6.0/06.sprinkles/chapter.md @@ -1,13 +1,15 @@ --- title: Sprinkles description: Sprinkles are modular units of code and content that implement some feature or override some default behavior of UserFrosting. -obsolete: true +wip: 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..07a7242a 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,20 @@ --- 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. +wip: true --- -[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). +Here's a problem every developer faces: **how do you write code that's easy to test and modify?** 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 + +[Dependency Injection](http://www.phptherightway.com/#dependency_injection) is one of the fundamental pillars of modern object-oriented software 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). For example, if you have class `Owl`: @@ -102,7 +112,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..5af4cb92 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,7 +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 +wip: true --- ### The Dependency Injection (DI) Container @@ -30,7 +30,7 @@ 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**. +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. @@ -41,7 +41,7 @@ This is where the **dependency injection container (DIC)** comes into play. The 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. @@ -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..31b9be00 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,10 @@ --- title: Default Services description: UserFrosting's default services provide most of the tools needed to build a basic web application. -obsolete: true +wip: 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. @@ -112,7 +112,7 @@ 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`* 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..ffb362ea 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,7 +1,7 @@ --- title: Adding Services description: You may extend UserFrosting's default services for additional functionality, or define completely new services in your Sprinkles. -obsolete: true +wip: 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. @@ -21,7 +21,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** 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..f9f20f6e 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,7 @@ --- 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 +wip: 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..b8ea5fcb 100644 --- a/app/pages/6.0/07.dependency-injection/chapter.md +++ b/app/pages/6.0/07.dependency-injection/chapter.md @@ -1,13 +1,17 @@ --- 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 +wip: 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..4f13e6a7 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,18 @@ --- 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 +wip: 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..8d875d0b 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,7 @@ --- 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 +wip: 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..aa20fff9 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,7 @@ --- 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 +wip: true --- ## RESTful Responses @@ -95,7 +95,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..f4a859c1 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,7 @@ --- title: RESTful Design description: Representational State Transfer (REST) is a design paradigm for efficient, scalable communication between clients and the server. -obsolete: true +wip: 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..1d8feb0a 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,7 @@ --- title: Front Controller description: The front controller consists of the route definitions that UserFrosting uses to process incoming requests from the client. -obsolete: true +wip: 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 +11,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..1d4a5d0f 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,7 @@ --- title: Controller Classes description: Controller classes allow you to easily separate the logic for your routes from your endpoint definitions. -obsolete: true +wip: 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 +114,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..5706cca4 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,15 @@ --- title: Registering Routes description: Once your routes definitions are ready, you have to register them inside your Sprinkle Recipe. -obsolete: true +wip: 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 +30,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 +46,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..0757643a 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,7 @@ --- 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 +wip: 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..0189389f 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,7 @@ --- 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 +wip: 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..f7c796ed 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,7 @@ --- 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 +wip: 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 +55,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 +85,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..56e1ae46 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,6 @@ --- title: AJAX Requests -obsolete: true +wip: true --- 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 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..a4b560e4 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,7 @@ --- title: Client Input description: Retrieving client input (GET, POST, PUT, DELETE, and URL arguments) in your controllers. -obsolete: true +wip: 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..dad8b126 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,15 @@ --- 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 +wip: 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 +
+
+``` + +**Vue Component** (`UserProfile.vue`): +```vue + + + +``` + +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 + + + + + + + + +``` + +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 ` + + +``` + +**Why we prefer ` + + +``` + +## Template Syntax + +Vue templates look like HTML but with special directives for dynamic behavior. + +### Text Interpolation + +Display reactive data with double curly braces: + +```vue + +``` + +### Attribute Binding (v-bind / :) + +Bind reactive data to HTML attributes: + +```vue + +``` + +### Event Handling (v-on / @) + +Listen to DOM events: + +```vue + + + +``` + +### Conditional Rendering + +**v-if / v-else-if / v-else** - Elements added/removed from DOM: + +```vue + +``` + +**v-show** - Elements stay in DOM, just toggle `display`: + +```vue + +``` + +### List Rendering (v-for) + +Loop through arrays or objects: + +```vue + +``` + +> [!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 + + + +``` + +## Component Communication + +### Props (Parent → Child) + +Pass data from parent to child components: + +**Parent.vue**: +```vue + + + +``` + +**UserCard.vue**: +```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 + + + + + +``` + +### 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 #} +
+ +
+ +{# Load your compiled assets #} +{% block scripts_page %} + {{ vite_js('main.ts') }} +{% endblock %} +``` + +## Composables (Reusable Logic) + +Extract and reuse logic across components with composables: + +**composables/useAuth.ts**: +```typescript +import { ref, computed } from 'vue' +import axios from 'axios' + +export function useAuth() { + const user = ref(null) + const loading = ref(false) + const error = ref(null) + + const isLoggedIn = computed(() => user.value !== null) + + async function login(username: string, password: string) { + loading.value = true + error.value = null + + try { + const response = await axios.post('/api/login', { + username, + password + }) + user.value = response.data.user + } catch (e) { + error.value = e as Error + } finally { + loading.value = false + } + } + + function logout() { + user.value = null + } + + return { + user, + loading, + error, + isLoggedIn, + login, + logout + } +} +``` + +**Usage in component**: +```vue + + + +``` + +## 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 + + + +``` + +## 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 + + + + + +``` + +## Form Handling Patterns + +### 1. Two-Way Data Binding + +Use `v-model` for automatic synchronization between form inputs and data: + +```vue + + + ``` -You can then initialize the `ufForm` widget on the `form` element in your page Javascript: +### 2. Form Validation -```js -$("#sign-in").ufForm({ - validators: page.validators.login, - msgTarget: $("#alerts-page") -}).on("submitSuccess.ufForm", function(event, data, textStatus, jqXHR) { - redirectOnLogin(jqXHR); -}); +#### Client-Side Validation + +Validate before submitting: + +```typescript +interface FormErrors { + [key: string]: string +} + +const errors = ref({}) + +function validateEmail(email: string): boolean { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + return regex.test(email) +} + +function validateForm(): boolean { + errors.value = {} + + // Required fields + if (!form.value.username.trim()) { + errors.value.username = 'Username is required' + } else if (form.value.username.length < 3) { + errors.value.username = 'Username must be at least 3 characters' + } + + // Email validation + if (!form.value.email) { + errors.value.email = 'Email is required' + } else if (!validateEmail(form.value.email)) { + errors.value.email = 'Please enter a valid email' + } + + // Password strength + if (form.value.password.length < 8) { + errors.value.password = 'Password must be at least 8 characters' + } + + return Object.keys(errors.value).length === 0 +} +``` + +#### Server-Side Validation + +Handle validation errors from the server: + +```typescript +async function handleSubmit() { + try { + await axios.post('/api/users', form.value) + } catch (error: any) { + if (error.response?.status === 422) { + // Laravel-style validation errors + errors.value = error.response.data.errors + } else if (error.response?.data?.errors) { + // UserFrosting-style errors + errors.value = error.response.data.errors + } + } +} +``` + +### 3. CSRF Protection + +UserFrosting requires CSRF tokens for POST requests. Include them automatically: + +```vue + + + ``` -The form will automatically be validated and submitted when the user clicks the `submit` button (` +``` + +### 4. Show Field-Level Errors + +Display errors next to the relevant field: + +```vue +
+ +
+ {{ errors.email }} +
+
+``` + +### 5. Reset Forms After Success + +Clear the form after successful submission: + +```typescript +async function handleSubmit() { + await submit('/api/users') + // Reset form + form.value = { ...initialData } + errors.value = {} +} +``` + +## What's Next? + +- **[Tables](client-side-code/components/tables)**: Display data in sortable, filterable tables +- **[Collections](client-side-code/components/collections)**: Manage dynamic lists of items +- **[Alerts](client-side-code/components/alerts)**: Show notifications to users + +## Further Reading -Triggered when the form submission has returned an error. This happens after the submission button has been re-enabled and any error messages have been rendered. +- [Vue 3 Form Handling](https://vuejs.org/guide/essentials/forms.html) +- [VeeValidate Documentation](https://vee-validate.logaretm.com/v4/) +- [Axios Documentation](https://axios-http.com/docs/intro) +- [UIkit Forms](https://getuikit.com/docs/form) diff --git a/app/pages/6.0/15.client-side-code/04.components/02.tables/docs.md b/app/pages/6.0/15.client-side-code/04.components/02.tables/docs.md index 7b7a91cd..eb9cd804 100644 --- a/app/pages/6.0/15.client-side-code/04.components/02.tables/docs.md +++ b/app/pages/6.0/15.client-side-code/04.components/02.tables/docs.md @@ -1,524 +1,364 @@ --- -title: Tables -description: ufTable is a wrapper for Mottie's tablesorter plugin that automatically fetches JSON data from a specified API endpoint, and dynamically builds paginated, sorted, filtered views on the fly. -obsolete: true +title: Data Tables with Vue 3 +description: Build sortable, filterable, paginated tables using Vue 3 and modern table libraries +wip: true --- -A typical application feature is to display a table of entities from a server-side data source (e.g., a database). For example, the `admin` Sprinkle generates client-side tables for admins to view and manage users, groups, roles, activities, and permissions: +Data tables are essential for displaying collections of information. UserFrosting 6.0 uses Vue 3 to create interactive tables with sorting, filtering, and pagination. -![Viewing a table of user activities](images/table-activities.png) +> [!NOTE] +> [TODO: Screenshot] - Modern data table with sorting/filtering -`ufTable` provides a convenient way to generate sortable, searchable, paginated tables of data from an AJAX source using Mottie's [tablesorter](https://mottie.github.io/tablesorter/docs/) jQuery plugin. +## Simple Table Component -## Table skeleton +Start with a basic sortable table: -A typical use case is to create a "skeleton" `` in your Twig template, and then use `ufTable` to dynamically retrieve data from a JSON data source and construct the rows. As the user sorts columns, inputs filter queries, and pages through the data, `ufTable` will submit new AJAX requests to the server and refresh the `
` with the results of the updated queries. +```vue +