7. Custom records

The basics are behind us, now let’s get deeper into the system and create a new record type, like tt_address which can be displayed through our plugin.

7.1. TCA

Task

Create necessary TCA for our new record.

Before we can do anything with Extbase, we need to configure TYPO3. The TCA (=Table Configuration Array) contains configuration for each database table. TYPO3 will generate the list view and edit forms within the Backend from this configuration.

Extbase uses this configuration for mapping and database queries, together with relation handling.

TYPO3 provides a rich documentation about the TCA at https://docs.typo3.org/typo3cms/TCAReference/. That’s why this section is empty, all information are available there.

One thing to notice is that Extbase uses “Convention over Configuration”. While we can configure Extbase to map a Model to a specific database table, we can auto match them. For a Model \Workshop\ExampleExtension\Domain\Model\Address, the database table would be tx_exampleextension_domain_model_address. So this will be our database table name for our example. Also TYPO3 uses convention over configuration, so the TCA for this table is placed within Configuration/TCA/tx_exampleextension_domain_model_address.php within our Extension.

Also each property within the model is written lowerCamelCase, while the database columns are written snake_case.

Our new record will be an address record with the following fields:

  • Company Name
  • Street
  • House number
  • Zip
  • City
  • Country

Once we finished the TCA, we already can create new records. Only saving them does not work, as we didn’t setup the database yet.

Note

By default new records are only allowed on pages of type “Folder”.

7.2. ext_tables.sql

Task

Create necessary sql for our new record.

Task

Create some records, edit them, play around.

Once the TCA is provided, we need to create the table in our Database. Each extension can provide a ext_tables.sql in the root directory. Within the admin tools and TYPO3 Console, you can update the database schema to match the current necessary structure of all extensions.

If multiple extensions adjust the same field, the last one in load order is used.

The example ext_tables.sql is:

1
2
3
4
5
6
7
8
CREATE TABLE tx_exampleextension_domain_model_address (
    company_name varchar(255) DEFAULT '' NOT NULL,
    street varchar(255) DEFAULT '' NOT NULL,
    house_number varchar(255) DEFAULT '' NOT NULL,
    zip varchar(255) DEFAULT '' NOT NULL,
    city varchar(255) DEFAULT '' NOT NULL,
    country varchar(255) DEFAULT '' NOT NULL,
);

All further TYPO3 specific fields, like uid and pid are added by TYPO3 CMS since v9.

Before v9, the file would look like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
CREATE TABLE tx_exampleextension_domain_model_address (
    uid int(11) unsigned NOT NULL auto_increment,
    pid int(11) unsigned DEFAULT '0' NOT NULL,

    crdate int(11) unsigned DEFAULT '0' NOT NULL,
    cruser_id int(11) unsigned DEFAULT '0' NOT NULL,
    tstamp int(11) unsigned DEFAULT '0' NOT NULL,
    hidden tinyint(3) unsigned DEFAULT '0' NOT NULL,
    deleted tinyint(3) unsigned DEFAULT '0' NOT NULL,
    starttime int(11) unsigned DEFAULT '0' NOT NULL,
    endtime int(11) unsigned DEFAULT '0' NOT NULL,

    company_name varchar(255) DEFAULT '' NOT NULL,
    street varchar(255) DEFAULT '' NOT NULL,
    house_number varchar(255) DEFAULT '' NOT NULL,
    zip varchar(255) DEFAULT '' NOT NULL,
    city varchar(255) DEFAULT '' NOT NULL,
    country varchar(255) DEFAULT '' NOT NULL,

    PRIMARY KEY (uid),
    KEY parent (pid)
);

We should now be able to create and save new records within the TYPO3 backend. Also existing records should be listed, searchable and editable.

7.3. Model

Task

Create Model representing our records.

Once we are able to create and edit records in TYPO3 backend, we are ready to go with Extbase. First we need a representation of our Data. This is done with a Model, in our case this has to match the table name and is called Workshop\ExampleExtension\Domain\Model\Address and located at Classes/Domain/Model/Address.php.

Each model is a PHP class, structured like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php

namespace Workshop\ExampleExtension\Domain\Model;

use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Address extends AbstractEntity
{
    /**
     * @var string
     */
    protected $companyName;

    public function getCompanyName(): string
    {
        return $this->companyName;
    }
}

7.4. Repository

Task

Create Repository to access records.

In order to get, update or delete our records, we need a repository. This will return the models for us. The repository is another class which can be completely empty:

The file is located at Classes/Domain/Repository/AddressRepository.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php

namespace Workshop\ExampleExtension\Domain\Repository;

use TYPO3\CMS\Extbase\Persistence\Repository;

class AddressRepository extends Repository
{

}

The parent class already provides all necessary methods for daily use cases.

7.5. Controller

Task

Provide available records to template.

In order to provide records in form of models to our template, we first need an instance of our repository:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace Workshop\ExampleExtension\Controller;

use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
use Workshop\ExampleExtension\Domain\Model\Address;
use Workshop\ExampleExtension\Domain\Repository\AddressRepository;

class AddressController extends ActionController
{
    /**
     * @var AddressRepository
     */
    protected $addressRepository;

    public function __construct(AddressRepository $addressRepository)
    {
        $this->addressRepository = $addressRepository;
    }

With the above code we only can create instances of the controller if an instance of the Repository is provided.

Extbase itself will analyse dependencies inside __construct and will provide instances. This is called Dependency Injection and works in three different ways with Extbase. The above one is the preferred as this is not Extbase specific but will also work in other PHP Frameworks and without any Dependency Injection at all.

We then can call the accordingly method to fetch records, which then can be assigned to the view:

1
2
3
4
5
6
class AddressController extends ActionController

    public function indexAction()
    {
        $this->view->assign('addresses', $this->addressRepository->findAll());
    }

The AddressRepository extends the base Repository class and inherits some methods, e.g. findAll().

7.6. Template

With our records in our template, we can iterate over them to display them.

Resources/Private/Templates/Address/Index.html:

1
2
3
4
5
6
7
8
<f:for each="{addresses}" as="address">
    <h3>{address.companyName}</h3>
    <address>
        {address.street} {address.houseNumber}
        {address.zip} {address.city}
        {address.country}
    </address>
</f:for>

7.7. Configure storage pid

We should not see any addresses yet, that’s due to the generated database query by Extbase. If no storage pid is configured, Extbase will fetch records from pid 0.

Within the content element we can select arbitrary “Record Storage Page” entries to use for records.

We could also configure the pid via TypoScript:

1
2
3
4
5
6
7
plugin {
    tx_exampleextension {
        persistence {
            storagePid = 2
        }
    }
}

7.8. Check everything

Once everything is set up, the following should be possible:

  • Create, edit, search and delete records within TYPO3 Backend.
  • List available records via Frontend Plugin.

Sounds like a lot of work for a small benefit? Right. If all you have to achieve is this, you should not use Extbase but “pure” TYPO3. But we will extend the Extension in the next step. Also this is about a first simple Extbase Extension, not how to use TYPO3 the right way.