TASK: Add configuration and custom record content

This commit is contained in:
Daniel Siepmann 2018-09-05 13:22:42 +02:00
parent e47a16656d
commit f7f4be0335
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
9 changed files with 590 additions and 3 deletions

View file

@ -31,6 +31,6 @@ class ExampleController extends ActionController
// Comment the code out, to use fluid template from
// "Resources/Private/Templates/Example/Example.html"
// return 'Hello world!';
return 'Hello world!';
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace Workshop\ExampleExtension\Domain\Model;
/*
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
class Address extends AbstractEntity
{
/**
* @var string
*/
protected $companyName;
/**
* @var string
*/
protected $street;
/**
* @var string
*/
protected $houseNumber;
/**
* @var string
*/
protected $zip;
/**
* @var string
*/
protected $city;
/**
* @var string
*/
protected $country;
public function getCompanyName(): string
{
return $this->companyName;
}
public function getStreet(): string
{
return $this->street;
}
public function getHouseNumber(): string
{
return $this->houseNumber;
}
public function getZip(): string
{
return $this->zip;
}
public function getCity(): string
{
return $this->city;
}
public function getCountry(): string
{
return $this->country;
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Workshop\ExampleExtension\Domain\Repository;
/*
* Copyright (C) 2018 Daniel Siepmann <coding@daniel-siepmann.de>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
use TYPO3\CMS\Extbase\Persistence\Repository;
class AddressRepository extends Repository
{
}

View file

@ -0,0 +1,101 @@
<?php
return (function (
$extensionKey = 'example_extension',
$tableName = 'tx_workshopexampleextension_domain_model_address'
) {
$extensionLanguagePrefix = 'LLL:EXT:example_extension/Resources/Private/Language/locallang_tca.xlf:';
$coreLanguagePrefix = 'LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:';
return [
'ctrl' => [
'label' => 'company_name',
'default_sortby' => 'company_name',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'cruser_id' => 'cruser_id',
'title' => $extensionLanguagePrefix . 'address',
'delete' => 'deleted',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime'
],
'searchFields' => 'company_name, street, city'
],
'interface' => [
'showRecordFieldList' => 'company_name, street, house_number, zip, city, country'
],
'palettes' => [
'address' => [
'showitem' => implode(',', [
'street, house_number',
'--linebreak--',
'zip, city',
'--linebreak--',
'country',
]),
],
],
'types' => [
'0' => [
'showitem' => implode(',', [
'--div--;' . $coreLanguagePrefix . 'general',
'company_name;;address',
]),
],
],
'columns' => [
'company_name' => [
'label' => $extensionLanguagePrefix . 'company_name',
'config' => [
'type' => 'input',
'max' => 255,
'eval' => 'trim,required',
],
],
'street' => [
'label' => $extensionLanguagePrefix . 'street',
'config' => [
'type' => 'input',
'max' => 255,
'eval' => 'trim,required',
],
],
'house_number' => [
'label' => $extensionLanguagePrefix . 'house_number',
'config' => [
'type' => 'input',
'size' => 10,
'max' => 255,
'eval' => 'trim,required',
],
],
'zip' => [
'label' => $extensionLanguagePrefix . 'zip',
'config' => [
'type' => 'input',
'size' => 10,
'max' => 255,
'eval' => 'trim,required',
],
],
'city' => [
'label' => $extensionLanguagePrefix . 'city',
'config' => [
'type' => 'input',
'max' => 255,
'eval' => 'trim,required',
],
],
'country' => [
'label' => $extensionLanguagePrefix . 'country',
'config' => [
'type' => 'input',
'max' => 255,
'eval' => 'trim,required',
],
],
],
];
})();

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="messages" date="2018-09-05T08:51:01Z" product-name="CodeExamples">
<header/>
<body>
<trans-unit id="address" xml:space="preserve">
<source>Address</source>
</trans-unit>
<trans-unit id="company_name" xml:space="preserve">
<source>Company name</source>
</trans-unit>
<trans-unit id="street" xml:space="preserve">
<source>Street</source>
</trans-unit>
<trans-unit id="house_number" xml:space="preserve">
<source>House number</source>
</trans-unit>
<trans-unit id="zip" xml:space="preserve">
<source>Zip</source>
</trans-unit>
<trans-unit id="city" xml:space="preserve">
<source>City</source>
</trans-unit>
<trans-unit id="country" xml:space="preserve">
<source>Country</source>
</trans-unit>
</body>
</file>
</xliff>

View file

@ -0,0 +1,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)
);

View file

@ -1,14 +1,138 @@
Configuration
=============
There are many different ways and places in TYPO3 for configuration. We will cover
the different places and types of configuration, including when to use a certain way.
By following the API you also make sure modern approaches like configuration loaders
will work with your extensions.
Places of configuration
-----------------------
The following places exist to configure a TYPO3 Extensions:
PHP
^^^
PHP via :file:`LocalConfiguration.php` and :file:`AdditionalConfiguration.php`.
Some extensions allow to place configuration in custom files, e.g. EXT:realurl. I
would call that a bad practice as you have arbitrary places to check for certain
configurations.
Instead use existing places like the two mentioned files above. This is done by
either using:
.. code-block:: php
return [
'EXTCONF' => [
'extkey' => [
// options
],
],
];
This way you can access the configuration via ``$GLOBALS['EXTCONF']['extkey']``.
Or by providing a :file:`ext_conf_template.txt` in the root of your Extension.
The content is TypoScript as documented at :ref:`t3coreapi:extension-options`.
Afterwards you can access the options through an API.
TypoScript
^^^^^^^^^^
TypoScript and Flexforms are merged by Extbase, so you do not have to do any
additional work and combine both. They are available as Variable ``{settings}`` in
all Templates. Also inside the Controller they are available as array in
``$this->settings`` out of the box.
.. admonition:: Task
Add some settings and print them in Template and Controller.
The configuration via TypoScript has to be located at a specific path:
.. code-block:: typoscript
:linenos:
// For all frontend plugins of the extension
plugin {
tx_exampleextension {
settings {
// The configuration goes here
}
}
}
// For a specific frontend plugin of the extension
plugin {
tx_exampleextension_pluginkey {
settings {
// The configuration goes here
}
}
}
// For Backend Modules
module {
tx_exampleextension {
settings {
// The configuration goes here
}
}
}
Extbase itself already has some configuration options available via TypoScript, some
are mentioned at :ref:`t3extbasebook:typoscript_configuration` section of Extbase
book.
.. tip::
The whole ``settings`` array is passed into all templates, layouts and partials.
This way it's possible for integrators to provide arbitary information.
Also it's possible to insert a plugin via TypoScript. In that case the settings can
be provided only for that instance:
.. code-block:: typoscript
:linenos:
lib.instance = USER
lib.instance {
userFunc = TYPO3\CMS\Extbase\Core\Bootstrap->run
extensionName = ExampleExtension
pluginName = pluginkey
vendorName = Workshop
settings {
testKey = testValue
}
}
Flexforms
^^^^^^^^^
Flexforms are like TCA, which will be covered later on. The format is XML instead of
PHP and saved inside the database field ``pi_flexform`` of the ``tt_content`` record.
This way editors are able to adjust provided settings within a plugin record.
Custom
^^^^^^
Do whatever you want, e.g. use yaml or TypoScript by calling the parser for contents
from anywhere.
When to use which
-----------------
The Flexform approach provides the best UX as it uses the known UI of TYPO3 inside a
record.
The TypoScript provided the best UX when integrators have to deploy configuration or
configuration is necessary on multiple pages. Also if the plugin is inserted directly
via TypoScript.
The PHP approach is best suited for instance wide configuration, which nearly never
exists. Things like API Keys might depend on the current Domain or Website, and there
can be multiple in a single TYPO3 instance.

View file

@ -1,14 +1,208 @@
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.
TCA
---
.. admonition:: 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 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 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
.. note::
By default new records are only allowed on pages of type "Folder".
ext_tables.sql
--------------
.. admonition:: Task
Create necessary sql for our new record.
.. admonition:: 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 :file:`ext_tables.sql` in the root directory. Within the
install tool 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 :file:`ext_tables.sql` is:
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/ext_tables.sql
:language: sql
Model
-----
.. admonition:: 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
:file:`Classes/Domain/Model/Address.php`.
Each model is a PHP class structure like:
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Domain/Model/Address.php
:language: php
:linenos:
:lines: 1-4,24-32,58-61,87
Repository
----------
.. admonition:: 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 :file:`Classes/Domain/Repository/AddressRepository.php`:
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Domain/Repository/AddressRepository.php
:language: php
:linenos:
:lines: 1-4, 24-
The parent class already provides all necessary methods for daily use cases.
Controller
----------
Model
-----
.. admonition:: 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:
.. code-block:: php
:linenos:
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use Workshop\ExampleExtension\Domain\Repository\AddressRepository;
class ExampleController 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:
.. code-block:: php
:linenos:
class ExampleController extends ActionController
{
public function exampleAction()
{
$this->view->assign('addresses', $this->addressRepository->findAll());
}
}
The ``AddressRepository`` extends the base ``Repository`` class and inherits some
methods, e.g. ``findAll``.
Template
--------
With our records in our template, we can iterate over them to display them.
:file:`Resources/Private/Templates/Example/Example.html`:
.. code-block:: html
:linenos:
<f:for each="{addresses}" as="address">
<h2>{address.companyName}</h2>
<address>
{address.street} {address.houseNumber}
{address.zip} {address.city}
{address.country}
</address>
</f:for>
Configure storage pid
---------------------
We should not see any addresses yet, thats 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:
.. code-block:: typoscript
:linenos:
plugin {
tx_exampleextension {
persistence {
storagePid = 2
}
}
}

View file

@ -308,6 +308,7 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
't3coreapi': ('https://docs.typo3.org/typo3cms/CoreApiReference/', None),
't3extbasebook': ('https://docs.typo3.org/typo3cms/ExtbaseFluidBook/', None),
}
# Allow inline PHP highlighting