diff --git a/docs/en/quickstart.md b/docs/en/quickstart.md index 702cb1575f..4e0ee90973 100644 --- a/docs/en/quickstart.md +++ b/docs/en/quickstart.md @@ -1,7 +1,52 @@ # Quick Start Guide The best way to experience and learn CakePHP is to sit down and build something. -To start off we'll build a simple Content Management application. +In this tutorial, we'll build a simple **Content Management System (CMS)** application that demonstrates the core features of CakePHP. + +## What You'll Build + +By the end of this tutorial, you'll have a fully functional CMS with: + +- ✅ **User Authentication** - Secure login system +- ✅ **Article Management** - Create, read, update, and delete articles +- ✅ **Tag System** - Organize content with tags +- ✅ **Database Relations** - Users, articles, and tags working together +- ✅ **Form Validation** - Input validation and error handling +- ✅ **Clean URLs** - SEO-friendly slug-based routing + +::: tip Estimated Time +This tutorial takes approximately **45-60 minutes** to complete from start to finish. +::: + +## What You'll Learn + +This hands-on tutorial covers: + +- Setting up a CakePHP application +- Database migrations and schema management +- Creating models with the ORM +- Building controllers and actions +- Rendering views and templates +- Form handling and validation +- Authentication and authorization +- Working with associations (relationships) + +::: details Prerequisites +Before starting, make sure you have: + +| Requirement | Version | +|-------------|---------| +| PHP | |minphpversion| - |phpversion| | +| Composer | Latest | +| Database | MySQL 5.7+, PostgreSQL 9.6+, or SQLite 3 | +| Web Server | PHP built-in server (for development) | + +**Basic PHP knowledge is recommended** but not strictly required. +::: + +## Getting Started + +Let's begin by installing CakePHP and setting up your development environment. @@ -10,3 +55,28 @@ To start off we'll build a simple Content Management application. + +## What's Next? + +🎉 **Congratulations!** You've built your first CakePHP application and learned the fundamentals of the framework. + +### Continue Building + +Ready to enhance your CMS? Here are some features you could add next: + +- **[Authentication & Authorization](../tutorials-and-examples/cms/authentication)** - Secure your application with user login +- **[Tags & Categories](../tutorials-and-examples/cms/tags-and-users)** - Add tagging functionality to organize articles + +### Explore CakePHP Features + +Dive deeper into CakePHP's powerful features: + +- **[ORM & Database](../orm)** - Advanced queries, associations, and behaviors +- **[Validation](../core-libraries/validation)** - Complex validation rules and custom validators +- **[Security](../security)** - CSRF protection, encryption, and security best practices +- **[Testing](../development/testing)** - Write unit and integration tests +- **[Deployment](../deployment)** - Deploy your application to production + +### Get Help & Connect + + diff --git a/docs/en/tutorials-and-examples/cms/articles-controller.md b/docs/en/tutorials-and-examples/cms/articles-controller.md index b6721fd5f0..2e46e86a71 100644 --- a/docs/en/tutorials-and-examples/cms/articles-controller.md +++ b/docs/en/tutorials-and-examples/cms/articles-controller.md @@ -6,9 +6,10 @@ methods, to prepare the response. We'll place this new controller in a file called **ArticlesController.php** inside the **src/Controller** directory. Here's what the basic controller should look like: -``` php +``` php {3} paginate($this->Articles); $this->set(compact('articles')); @@ -126,10 +128,10 @@ correctly formatted with the title and table listing of the articles. If you were to click one of the 'view' links in our Articles list page, you'd see an error page saying that action hasn't been implemented. Lets fix that now: -``` php +``` php {3} // Add to existing src/Controller/ArticlesController.php file -public function view($slug = null) +public function view(?string $slug): void { $article = $this->Articles->findBySlug($slug)->firstOrFail(); $this->set(compact('article')); @@ -153,7 +155,7 @@ page telling us we're missing a view template; let's fix that. Let's create the view for our new 'view' action and place it in **templates/Articles/view.php** -``` php +``` php {3-4}
= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
``` +::: tip Security: XSS Protection +The `h()` helper function escapes output to prevent XSS (Cross-Site Scripting) attacks. Always use it when outputting user-generated content. +::: + You can verify that this is working by trying the links at `/articles/index` or manually requesting an article by accessing URLs like `/articles/view/first-post`. @@ -172,28 +178,30 @@ With the basic read views created, we need to make it possible for new articles to be created. Start by creating an `add()` action in the `ArticlesController`. Our controller should now look like: -``` php +``` php {3,11,17,23} paginate($this->Articles); $this->set(compact('articles')); } - public function view($slug) + public function view(?string $slug): void { $article = $this->Articles->findBySlug($slug)->firstOrFail(); $this->set(compact('article')); } - public function add() + public function add(): void { $article = $this->Articles->newEmptyEntity(); if ($this->request->is('post')) { @@ -215,10 +223,11 @@ class ArticlesController extends AppController } ``` -> [!NOTE] -> You need to include the [Flash](../../controllers/components/flash) component in -> any controller where you will use it. Often it makes sense to include it in -> your `AppController`, which is there already for this tutorial. +::: info Flash Component +You need to include the [Flash](../../controllers/components/flash) component in +any controller where you will use it. Often it makes sense to include it in +your `AppController`, which is there already for this tutorial. +::: Here's what the `add()` action does: @@ -304,15 +313,15 @@ creating a slug attribute, and the column is `NOT NULL`. Slug values are typically a URL-safe version of an article's title. We can use the [beforeSave() callback](../../orm/table-objects#table-callbacks) of the ORM to populate our slug: -``` php +``` php {3,7-9,13} Articles ->findBySlug($slug) @@ -430,11 +437,11 @@ articles: Up until this point our Articles had no input validation done. Lets fix that by using [a validator](../../orm/validation#validating-request-data): -``` php +``` php {5,8} // src/Model/Table/ArticlesTable.php -// add this use statement right below the namespace declaration to import -// the Validator class +// add this use statement right below the namespace declaration +// to import the Validator class use Cake\Validation\Validator; // Add the following method. @@ -472,12 +479,10 @@ automatically. Next, let's make a way for users to delete articles. Start with a `delete()` action in the `ArticlesController`: -``` php +``` php {3} // src/Controller/ArticlesController.php -// Add the following method. - -public function delete($slug) +public function delete(?string $slug): void { $this->request->allowMethod(['post', 'delete']); @@ -499,10 +504,11 @@ error page is displayed. There are many built-in [Exceptions](../../development/errors) that can be used to indicate the various HTTP errors your application might need to generate. -> [!WARNING] -> Allowing content to be deleted using GET requests is *very* dangerous, as web -> crawlers could accidentally delete all your content. That is why we used -> `allowMethod()` in our controller. +::: danger Security Warning +Allowing content to be deleted using GET requests is *very* dangerous, as web +crawlers could accidentally delete all your content. That is why we used +`allowMethod()` in our controller. +::: Because we're only executing logic and redirecting to another action, this action has no template. You might want to update your index template with links @@ -548,19 +554,21 @@ Using `Cake\View\Helper\FormHelper::deleteLink()` will create a link that uses JavaScript to do a DELETE request deleting our article. Prior to CakePHP 5.2 you need to use `postLink()` instead. -> [!NOTE] -> This view code also uses the `FormHelper` to prompt the user with a -> JavaScript confirmation dialog before they attempt to delete an -> article. - -> [!TIP] -> The `ArticlesController` can also be built with `bake`: -> -> ``` bash -> /bin/cake bake controller articles -> ``` -> -> However, this does not build the **templates/Articles/\*.php** files. +::: info FormHelper Confirmation +This view code also uses the `FormHelper` to prompt the user with a +JavaScript confirmation dialog before they attempt to delete an +article. +::: + +::: tip Use Bake to Generate Controllers +The `ArticlesController` can also be built with `bake`: + +``` bash +bin/cake bake controller articles +``` + +However, this does not build the **templates/Articles/\*.php** files. +::: With a basic articles management setup, we'll create the [basic actions for our Tags and Users tables](../../tutorials-and-examples/cms/tags-and-users). diff --git a/docs/en/tutorials-and-examples/cms/articles-model.md b/docs/en/tutorials-and-examples/cms/articles-model.md index 2b319f0a7e..d338fc9f2e 100644 --- a/docs/en/tutorials-and-examples/cms/articles-model.md +++ b/docs/en/tutorials-and-examples/cms/articles-model.md @@ -11,7 +11,7 @@ They are stored in **src/Model/Table**. The file we'll be creating will be saved to **src/Model/Table/ArticlesTable.php**. The completed file should look like this: -``` php +``` php {3,11} [!NOTE] -> CakePHP will dynamically create a model object for you if it -> cannot find a corresponding file in **src/Model/Table**. This also means -> that if you accidentally name your file wrong (i.e. articlestable.php or -> ArticleTable.php), CakePHP will not recognize any of your settings and will -> use the generated model instead. +::: info Automatic Model Creation +CakePHP will dynamically create a model object for you if it +cannot find a corresponding file in **src/Model/Table**. This also means +that if you accidentally name your file wrong (i.e. articlestable.php or +ArticleTable.php), CakePHP will not recognize any of your settings and will +use the generated model instead. +::: We'll also create an Entity class for our Articles. Entities represent a single record in the database and provide row-level behavior for our data. Our entity will be saved to **src/Model/Entity/Article.php**. The completed file should look like this: -``` php +``` php {3,11} [!TIP] -> The `ArticlesTable` and `Article` Entity classes can be generated from a -> terminal: -> -> ``` bash -> bin/cake bake model articles -> ``` +::: tip Use Bake to Generate Models +The `ArticlesTable` and `Article` Entity classes can be generated from a +terminal: + +``` bash +bin/cake bake model articles +``` +::: We can't do much with this model yet. Next, we'll create our first [Controller and Template](../../tutorials-and-examples/cms/articles-controller) diff --git a/docs/en/tutorials-and-examples/cms/database.md b/docs/en/tutorials-and-examples/cms/database.md index 44a214a620..a188664c75 100644 --- a/docs/en/tutorials-and-examples/cms/database.md +++ b/docs/en/tutorials-and-examples/cms/database.md @@ -4,10 +4,205 @@ Now that we have CakePHP installed, let's set up the database for our `CMS (Content Management System)` application. If you haven't already done so, create an empty database for use in this tutorial, with the name of your choice such as `cake_cms`. -If you are using MySQL/MariaDB, you can execute the following SQL to create the -necessary tables: -``` sql +## Database Configuration + +First, let's tell CakePHP where our database is and how to connect to it. Replace +the values in the `Datasources.default` array in your **config/app_local.php** file +with those that apply to your setup. A sample completed configuration array +might look something like the following: + +``` php {2} + [ + 'default' => [ + 'host' => 'localhost', + 'username' => 'cakephp', + 'password' => 'AngelF00dC4k3~', + 'database' => 'cake_cms', + 'url' => env('DATABASE_URL', null), + ], + ], + // More configuration below. +]; +``` + +Once you've saved your **config/app_local.php** file, you should see that the 'CakePHP is +able to connect to the database' section has a green chef hat. + +::: info Local Configuration +The file **config/app_local.php** is a local override of the file **config/app.php** +used to configure your development environment quickly. +::: + +## Creating Database Tables + +Choose your preferred approach for creating the database schema: + +::: tip Which Approach Should I Use? +**Migrations** (recommended) are version-controlled, database-agnostic, and perfect for team collaboration. +**Raw SQL** is fine for quick prototyping or if you prefer direct database control. +::: + +### Option A: Using Migrations (Recommended) + +Migrations provide a platform-independent way to manage your database schema, so you don't need to worry about the subtle differences between MySQL, PostgreSQL, SQLite, etc. + +::: code-group + +```bash [Commands] +# Generate migration files +bin/cake bake migration CreateUsers email:string password:string created modified +bin/cake bake migration CreateArticles user_id:integer title:string slug:string[191]:unique body:text published:boolean created modified +bin/cake bake migration CreateTags title:string[191]:unique created modified +bin/cake bake migration CreateArticlesTags article_id:integer:primary tag_id:integer:primary created modified + +# Run migrations to create tables +bin/cake migrations migrate +``` + +```php [Migration Example] +table('articles'); + $table->addColumn('user_id', 'integer') + ->addColumn('title', 'string', ['limit' => 255]) + ->addColumn('slug', 'string', ['limit' => 191]) + ->addColumn('body', 'text', ['null' => true]) + ->addColumn('published', 'boolean', ['default' => false]) + ->addColumn('created', 'datetime') + ->addColumn('modified', 'datetime') + ->addIndex(['slug'], ['unique' => true]) + ->addForeignKey('user_id', 'users', 'id') + ->create(); + } +} +``` + +::: + +::: warning Composite Primary Key Adjustment +The `articles_tags` migration will need manual adjustment. The generated migration sets both columns to auto-increment: + +```php +$table->addColumn('article_id', 'integer', [ + 'autoIncrement' => true, // [!code --] + 'default' => null, + 'limit' => 11, + 'null' => false, +]); +$table->addColumn('tag_id', 'integer', [ + 'autoIncrement' => true, // [!code --] + 'default' => null, + 'limit' => 11, + 'null' => false, +]); +``` + +Remove the `autoIncrement` lines before running the migration to prevent foreign key problems. +::: + +#### Adding Seed Data + +Create seed files to populate initial data: + +```bash +# Generate seed files +bin/cake bake seed Users +bin/cake bake seed Articles +``` + +Edit the generated seed files: + +::: code-group + +```php [UsersSeed.php] + 'cakephp@example.com', + 'password' => 'secret', + 'created' => date('Y-m-d H:i:s'), + 'modified' => date('Y-m-d H:i:s'), + ], + ]; + + $table = $this->table('users'); + $table->insert($data)->save(); + } +} +``` + +```php [ArticlesSeed.php] + 1, + 'title' => 'First Post', + 'slug' => 'first-post', + 'body' => 'This is the first post.', + 'published' => true, + 'created' => date('Y-m-d H:i:s'), + 'modified' => date('Y-m-d H:i:s'), + ], + ]; + + $table = $this->table('articles'); + $table->insert($data)->save(); + } +} +``` + +::: + +::: warning Password Security +The seed data above stores passwords in **plain text** for initial setup purposes only. This is a **security risk** and should **never be used in production**. We will properly implement password hashing when we add [authentication](../../tutorials-and-examples/cms/authentication) later in this tutorial. +::: + +Run the seeders: + +```bash +bin/cake migrations seed +``` + +### Option B: Using Raw SQL + +If you prefer to use direct SQL statements, you can execute these in your database client: + +::: code-group + +``` sql [MySQL/MariaDB] CREATE DATABASE cake_cms; USE cake_cms; @@ -58,10 +253,7 @@ VALUES (1, 'First Post', 'first-post', 'This is the first post.', 1, NOW(), NOW()); ``` -If you are using PostgreSQL, connect to the `cake_cms` database and execute the -following SQL instead: - -``` sql +``` sql [PostgreSQL] CREATE TABLE users ( id SERIAL PRIMARY KEY, email VARCHAR(255) NOT NULL, @@ -108,6 +300,10 @@ VALUES (1, 'First Post', 'first-post', 'This is the first post.', TRUE, NOW(), NOW()); ``` +::: + +## Understanding the Schema + You may have noticed that the `articles_tags` table uses a composite primary key. CakePHP supports composite primary keys almost everywhere, allowing you to have simpler schemas that don't require additional `id` columns. @@ -119,91 +315,8 @@ flexible enough to accommodate almost any database schema, adhering to the conventions will save you time as you can leverage the convention-based defaults CakePHP provides. -## Database Configuration - -Next, let's tell CakePHP where our database is and how to connect to it. Replace -the values in the `Datasources.default` array in your **config/app_local.php** file -with those that apply to your setup. A sample completed configuration array -might look something like the following: - -``` php - [ - 'default' => [ - 'host' => 'localhost', - 'username' => 'cakephp', - 'password' => 'AngelF00dC4k3~', - 'database' => 'cake_cms', - 'url' => env('DATABASE_URL', null), - ], - ], - // More configuration below. -]; -``` - -Once you've saved your **config/app_local.php** file, you should see that the 'CakePHP is -able to connect to the database' section has a green chef hat. - -> [!NOTE] -> The file **config/app_local.php** is a local override of the file **config/app.php** -> used to configure your development environment quickly. - -## Migrations - -The SQL statements to create the tables for this tutorial can also be generated -using the Migrations Plugin. Migrations provide a platform-independent way to -run queries so the subtle differences between MySQL, PostgreSQL, SQLite, etc. -don't become obstacles. - -``` bash -bin/cake bake migration CreateUsers email:string password:string created modified -bin/cake bake migration CreateArticles user_id:integer title:string slug:string[191]:unique body:text published:boolean created modified -bin/cake bake migration CreateTags title:string[191]:unique created modified -bin/cake bake migration CreateArticlesTags article_id:integer:primary tag_id:integer:primary created modified -``` - -> [!NOTE] -> Some adjustments to the generated code might be necessary. For example, the -> composite primary key on `articles_tags` will be set to auto-increment -> both columns: -> -> ``` bash -> $table->addColumn('article_id', 'integer', [ -> 'autoIncrement' => true, -> 'default' => null, -> 'limit' => 11, -> 'null' => false, -> ]); -> $table->addColumn('tag_id', 'integer', [ -> 'autoIncrement' => true, -> 'default' => null, -> 'limit' => 11, -> 'null' => false, -> ]); -> ``` -> -> Remove those lines to prevent foreign key problems. Once adjustments are -> done: -> -> ``` bash -> bin/cake migrations migrate -> ``` - -Likewise, the starter data records can be done with seeds. - -``` bash -bin/cake bake seed Users -bin/cake bake seed Articles -``` - -Fill the seed data above into the new `UsersSeed` and `ArticlesSeed` -classes, then: - - bin/cake migrations seed - -Read more about building migrations and data seeding: [Migrations](https://book.cakephp.org/migrations/4/) +::: tip Learn More +Read more about database migrations and seeding in the [Migrations documentation](https://book.cakephp.org/migrations/4/). +::: -With the database built, we can now build [Models](../../tutorials-and-examples/cms/articles-model). +With the database built, we can now build our [Models](../../tutorials-and-examples/cms/articles-model). diff --git a/docs/en/tutorials-and-examples/cms/installation.md b/docs/en/tutorials-and-examples/cms/installation.md index 4d8367038d..1d9e8ddb9b 100644 --- a/docs/en/tutorials-and-examples/cms/installation.md +++ b/docs/en/tutorials-and-examples/cms/installation.md @@ -19,7 +19,7 @@ Before starting you should make sure that you're using a supported PHP version: php -v ``` -You should at least have got installed PHP |minphpversion| (CLI) or higher. Your webserver's PHP version must also be of |minphpversion| or higher, and should be the same version your command line interface (CLI) PHP is. +You should have at least PHP |minphpversion| (CLI) or higher installed. Your webserver's PHP version must also be of |minphpversion| or higher, and should be the same version your command line interface (CLI) PHP is. ## Getting CakePHP @@ -28,10 +28,17 @@ of installing CakePHP from your terminal or command line prompt. First, you'll need to download and install Composer if you haven't done so already. If you have cURL installed, run the following: -``` bash +::: code-group +``` bash [Linux/macOS] curl -s https://getcomposer.org/installer | php ``` +``` bash [Windows] +# Download and run the Composer Windows Installer +# https://getcomposer.org/Composer-Setup.exe +``` +::: + Or, you can download `composer.phar` from the [Composer website](https://getcomposer.org/download/). @@ -39,17 +46,15 @@ Then simply type the following line in your terminal from your installation directory to install the CakePHP application skeleton in the **cms** directory of the current working directory: -``` bash -php composer.phar create-project cakephp/app:5 cms +::: code-group +``` bash [Linux/macOS] +php composer.phar create-project --prefer-dist cakephp/app:5.* cms ``` -If you downloaded and ran the [Composer Windows Installer](https://getcomposer.org/Composer-Setup.exe), then type the following line in -your terminal from your installation directory (ie. -C:\wamp\www\dev): - -``` bash -composer self-update && composer create-project cakephp/app:5.* cms +``` bash [Windows] +composer create-project --prefer-dist cakephp/app:5.* cms ``` +::: The advantage to using Composer is that it will automatically complete some important set up tasks, such as setting the correct file permissions and @@ -62,45 +67,55 @@ Regardless of how you downloaded and installed CakePHP, once your set up is completed, your directory setup should look like the following, though other files may also be present: - cms/ - bin/ - config/ - plugins/ - resources/ - src/ - templates/ - tests/ - tmp/ - vendor/ - webroot/ - composer.json - index.php - README.md +``` +cms/ + bin/ + config/ + plugins/ + resources/ + src/ + templates/ + tests/ + tmp/ + vendor/ + webroot/ + composer.json + index.php + README.md +``` Now might be a good time to learn a bit about how CakePHP's directory structure works: check out the [CakePHP Folder Structure](../../intro/cakephp-folder-structure) section. +::: tip Completed Tutorial If you get lost during this tutorial, you can see the finished result [on GitHub](https://github.com/cakephp/cms-tutorial). +::: -> [!TIP] -> The `bin/cake` console utility can build most of the classes and data -> tables in this tutorial automatically. However, we recommend following along -> with the manual code examples to understand how the pieces fit together and -> how to add your application logic. +::: tip Use Bake Wisely +The `bin/cake` console utility can build most of the classes and data +tables in this tutorial automatically. However, we recommend following along +with the manual code examples to understand how the pieces fit together and +how to add your application logic. +::: ## Checking our Installation We can quickly check that our installation is correct, by checking the default home page. Before you can do that, you'll need to start the development server: -``` bash +::: code-group +``` bash [Linux/macOS] cd /path/to/our/app bin/cake server ``` -> [!NOTE] -> For Windows, the command needs to be `bin\cake server` (note the backslash). +``` bash [Windows] +cd C:\path\to\our\app + +bin\cake server +``` +::: This will start PHP's built-in webserver on port 8765. Open up **http://localhost:8765** in your web browser to see the welcome page. All the diff --git a/toc_en.json b/toc_en.json index b6b22958f9..6177a7eee4 100644 --- a/toc_en.json +++ b/toc_en.json @@ -4,8 +4,8 @@ "text": "Getting Started", "collapsed": false, "items": [ - { "text": "Installation", "link": "/installation" }, { "text": "Quickstart", "link": "/quickstart" }, + { "text": "Installation", "link": "/installation" }, { "text": "Introduction", "link": "/intro" }, { "text": "Conventions", "link": "/intro/conventions" }, { @@ -407,7 +407,7 @@ { "text": "5.3 Migration Guide", "link": "/appendices/5-3-migration-guide" - }, + }, { "text": "CakePHP Development Process", "link": "/appendices/cakephp-development-process"