NOOB question from tutorial

Hey everyone,

I’m super new to cake and just work through the first tutorial on the main page and cant make the tag sections work. I can add end edit Articles and can also add and Edit Tags but cant set the Tags for the Aricles. I also don’t see changes in the database.
When i go to the Tags website and add or edit the tag i can change the corresponding articles and see this also in the database.

Here probably the 2 important files.

If someone could help me would be so :smiling_face:.

<!-- File: templates/Articles/add.php -->

<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
// Hard code the user for now.
echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]);
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '9']);
echo $this->Form->control('tags._ids', ['options' => $tags]);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>

<?php
// src/Controller/ArticlesController.php
namespace App\Controller;

use App\Controller\AppController;

class ArticlesController extends AppController
{
    public function index()
    {
        $articles = $this->paginate($this->Articles);
        $this->set(compact('articles'));
    }

    public function view($slug)
    {
        $article = $this->Articles->findBySlug($slug)->firstOrFail();
        $this->set(compact('article'));
    }

    public function add()
    {
        $article = $this->Articles->newEmptyEntity();
        if ($this->request->is('post')) {
            $article = $this->Articles->patchEntity($article, $this->request->getData());

            // Hardcoding the user_id is temporary, and will be removed later
            // when we build authentication out.
            $article->user_id = 1;

            if ($this->Articles->save($article)) {
                $this->Flash->success(__('Your article has been saved.'));

                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('Unable to add your article.'));
        }
        // Get a list of tags.
        $tags = $this->Articles->Tags->find('list')->all();

        // Set tags to the view context
        $this->set('tags', $tags);

        $this->set('article', $article);
    }

    public function edit($slug)
    {
        $article = $this->Articles
            ->findBySlug($slug)
            ->contain('Tags') // load associated Tags
            ->firstOrFail();

        if ($this->request->is(['post', 'put'])) {
            $this->Articles->patchEntity($article, $this->request->getData());
            if ($this->Articles->save($article)) {
                $this->Flash->success(__('Your article has been updated.'));

                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('Unable to update your article.'));
        }
        // Get a list of tags.
        $tags = $this->Articles->Tags->find('list')->all();

        // Set tags to the view context
        $this->set('tags', $tags);

        $this->set('article', $article);
    }

    public function delete($slug)
    {
        $this->request->allowMethod(['post', 'delete']);

        $article = $this->Articles->findBySlug($slug)->firstOrFail();
        if ($this->Articles->delete($article)) {
            $this->Flash->success(__('The {0} article has been deleted.', $article->title));

            return $this->redirect(['action' => 'index']);
        }
    }

    public function tags()
    {
        // The 'pass' key is provided by CakePHP and contains all
        // the passed URL path segments in the request.
        $tags = $this->request->getParam('pass');

        // Use the ArticlesTable to find tagged articles.
        $articles = $this->Articles->find('tagged', tags: $tags)
            ->all();

        // Pass variables into the view template context.
        $this->set([
            'articles' => $articles,
            'tags' => $tags
        ]);
    }
}


In your src/Model/Entity/Article.php is the property _accessible configured so that tags is accessible?

Hi Kevin, thank you very much for the quick reply and you are a genius.
You knew exactly where to look.

This line made it.

   'tags' => true,

I checked the tutorial again and think it is missing in the tutorial. For people like you probably super easy to find. For first day Tutorial users like me it would be great to add this info.

Here the file for other people who search for it.

<?php
// src/Model/Entity/Article.php
declare(strict_types=1);

namespace App\Model\Entity;

use Cake\ORM\Entity;

class Article extends Entity
{
    protected array $_accessible = [
        'title' => true,
        'body' => true,
        'published' => true,
        'created' => true,
        'modified' => true,
        'users' => true,
        // this line was missing
        'tags' => true,
    ];
}

Maybe you could explain me one more thing.
With 'tags' => true i could just add a field from another table.
How does $_accessible know that 'title' => true, is the one from the ‘Article’ table and not also from the ‘Tags’ table or can i access both if they have the same name?

It very well may be, that the docs are not up2date or are missing stuff. I will get that fixed asap.


To explain how this property is related to saving data in the form I want you to understand a few things how CakePHP’s ORM works.

Lets stay with the /articles/add URL since its easier to understand.
At first you call that URL and it goes through your controller action code, but all it really does in the first GET request is just execute these 4 lines:

$article = $this->Articles->newEmptyEntity();
$tags = $this->Articles->Tags->find('list')->all();
$this->set('tags', $tags);
$this->set('article', $article);

The big block where its saving the entity is not called since its not a POST request.
So all you really do now is create an empty article entity (without any data), a list of all existing tags and set both these items into the view so that the template can do stuff with it.

In your template you create a form $this->Form->create($article); which is “related” to that empty article entity you just created. All this is fine.

Now you created a field for tags which - of course - can have multiple values - tags._ids

The generated HTML is by default just a multi-select with the name tags[_ids][]. Why this name? Because thats how multiple values can be posted via PHP.

So now if we want to add an article and connect it to multple existing tags, we can check the submitted data via

pr($this->request->getData());

right at the start of the add() method in your ArticlesController.

It will show you something like

Array
(
    [user_id] => 1
    [title] => Testing
    [slug] => bla
    [body] => Test
    [published] => 0
    [tags] => Array
        (
            [_ids] => Array
                (
                    [0] => 1
                    [1] => 2
                    [2] => 3
                )

        )

)

which seems logical I’d say.

So this is the data which we want to fill into that previously created empty article.

This is done via

$article = $this->Articles->newEmptyEntity();
$article = $this->Articles->patchEntity($article, $this->request->getData());

So now you tell the ORM, that the request data needs to be patched/put into the empty article data.


But with CakePHP you can tell it which fields are “mass assignable” and which not.
Technically you could do something like this as well:

$article->title = $requestData['title'];
$article->slug = $requestData['slug'];
// etc.

but this gets tedious very quickly.

This also doesn’t check validators or rules which you can setup in the table class.

Thats why you can configure which fields inside the entity are allowed to be patched automatically => thats what you configure in the _accessible array (yea, the name is horrible, we will fix it in CakePHP 6 whenever that releases)

Anyway - since you now added

'tags' => true,

you told CakePHP, that it is allowed to patch the passed down request data for tags into the entity and therefore successfully saves it.

Hopefull this is a bit more understandable.

1 Like

Hello Kevin,

Thank you for the detailed explanation. What i understood in short is the following.

The OMR is an abstraction level of the database as variables of a class. We then derive an empty object from this class so that we can work with the variables. Only when we press the button is the data transferred to the database.

'tags' => true,

This line allows us to write the variable in the Object. Its a Object level restriction/allowance not a database restriction.

The general purpose of an ORM is an object oriented representation of your database and how to interact with it.

E.g. if you look at your edit method in your controller you can see, that you do

$article = $this->Articles
    ->findBySlug($slug)
    ->contain('Tags') // load associated Tags
    ->firstOrFail();

so you first fetch the existing entity from the database and then patch the given data

$this->Articles->patchEntity($article, $this->request->getData());

and finally save it

$this->Articles->save($article)

so patching only changes the entities values inside PHP, it is not “persisted” to the database.
Only when you call the save method via the table instance (which is available via the $this->Articles property in your controller) it will actually be put into your database.

1 Like

Hello Kevin, thank you for your great explanation.
Maybe its a good time to ask a small simple question than starting a new topic:

Whats the difference between
$article and $this->Articles in this line?

$article is an (empty) Entity which is representing the database object?

$this->Articles is a reference to the whole controller?

Thankl you so much!

$this->Articles references the property Articles, which is present inside your Controller. If you don’t know what properties are you need to learn some PHP OOP basics :wink:

CakePHP by default initializes the table instance of your controller automatically given the name of the Controller.

E.g.

  • BooksController will have a $this->Books
  • AuthorsController will have a $this->Authors

etc.

By initializing the trable instance I mean its the same thing as if you are doing:

$this->Books = $this->fetchTable('Books');