From e2067614cabc80265e9567799a1b7e3e1538fefd Mon Sep 17 00:00:00 2001 From: "lina.wolf" Date: Wed, 7 Sep 2022 09:26:27 +0200 Subject: [PATCH] Move Persistence chapters to TYPO3 Explained. (#581) https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ExtensionArchitecture/Extbase/Reference/Domain/Model.html Redirects are already in place Releases: main, 11.5 Co-authored-by: lina.wolf (cherry picked from commit 59ae71ca2036e173dba641c995247ceb59d8e723) --- .../6-Persistence/1-prepare-the-database.rst | 573 ------------- .../2-configure-the-backends-inputforms.rst | 755 ------------------ .../2a-creating-the-repositories.rst | 342 -------- ...-implement-individual-database-queries.rst | 686 ---------------- .../4-use-foreign-data-sources.rst | 57 -- Documentation/6-Persistence/Index.rst | 5 - 6 files changed, 2418 deletions(-) delete mode 100644 Documentation/6-Persistence/1-prepare-the-database.rst delete mode 100644 Documentation/6-Persistence/2-configure-the-backends-inputforms.rst delete mode 100644 Documentation/6-Persistence/2a-creating-the-repositories.rst delete mode 100644 Documentation/6-Persistence/3-implement-individual-database-queries.rst delete mode 100644 Documentation/6-Persistence/4-use-foreign-data-sources.rst diff --git a/Documentation/6-Persistence/1-prepare-the-database.rst b/Documentation/6-Persistence/1-prepare-the-database.rst deleted file mode 100644 index 7b90c5ff..00000000 --- a/Documentation/6-Persistence/1-prepare-the-database.rst +++ /dev/null @@ -1,573 +0,0 @@ -.. include:: /Includes.rst.txt -.. index:: - Database - Files; ext_tables.sql - -====================== -Preparing the database -====================== - -The preparation of the database primarily covers the creation of the -database tables. The commands for the creation are done in -*SQL*. The code is put into the file -:file:`ext_tables.sql`, which is located on the top level -of the extension directory. - -.. note:: - - One of the main purposes of Extbase is to abstract the - access of the underlying persistence solution. Thus, you normally won't - get in touch with native SQL-Queries in day-to-day development, especially - when you let the kickstarter auto-generate your database tables (have a - look at Chapter 10). However, you should fully understand all the - peculiarities of your database. - - -.. index:: Database; Domain object tables - -Preparing the tables of the domain objects -========================================== - -Let's have a look at the definition of the database table which will -aggregate the objects of the class -:php:`\MyVendor\SjrOffers\Domain\Model\Organization`: - -.. code-block:: mysql - :caption: ext_tables.sql - :name: ext-tables-sql - - CREATE TABLE tx_sjroffers_domain_model_organization ( - uid int(11) unsigned DEFAULT 0 NOT NULL auto_increment, - pid int(11) DEFAULT 0 NOT NULL, - - name varchar(255) NOT NULL, - address text NOT NULL, - telephone_number varchar(80) NOT NULL, - telefax_number varchar(80) NOT NULL, - url varchar(80) NOT NULL, - email_address varchar(80) NOT NULL, - description text NOT NULL, - image varchar(255) NOT NULL, - contacts int(11) NOT NULL, - offers int(11) NOT NULL, - administrator int(11) NOT NULL, - - tstamp int(11) unsigned DEFAULT 0 NOT NULL, - crdate int(11) unsigned DEFAULT 0 NOT NULL, - deleted tinyint(4) unsigned DEFAULT 0 NOT NULL, - hidden tinyint(4) unsigned DEFAULT 0 NOT NULL, - sys_language_uid int(11) DEFAULT 0 NOT NULL, - l18n_parent int(11) DEFAULT 0 NOT NULL, - l18n_diffsource mediumblob NOT NULL, - fe_group int(11) DEFAULT 0 NOT NULL, - - PRIMARY KEY (uid), - KEY parent (pid), - ); - -`CREATE TABLE` instructs the database to create a new -table named `tx_sjroffers_domain_model_organization`. The -table's name is derived from the Extbase convention, which describes that -class names are written in lowercase, retaining the underlines. - -.. note:: - - The file `ext_tables.sql` is executed whenever the - extension is installed. Nevertheless, TYPO3 is smart enough not to - overwrite an existing database table. On the contrary, it deduces the - differences between the new and the existing tables and adds that - additional information. - -The definition of the database table fields `name`, -`address` etc., follows in round brackets. Some of them should -sound familiar since they meet the properties' names of the class -`Organization`. - -.. note:: - - However, the Extbase convention is still - present: Field names are written in `lowercase_underscore` and are derived - from the property's name by prefixing every uppercase letter with an - underscore writing the whole construct in lowercase. The - value of the property `address` is saved in the field - `address`. The property `telephoneNumber` transforms - into the field name `telephone_number` etc. - -However, the table definition contains additional fields with -no correlating property in the class :php:`Organisation`. -TYPO3 needs them for providing functionalities like Localization, -Workspaces and Versioning. The according TYPO3-specific fields are: - -`uid` Describes the unique identifier associated with -every record within a database table *(unique record -identifier)*. - -`pid` Every page within a TYPO3 installation has a unique -page identifier (Page ID or PID). This may be System Folder -*(SysFolder)* or even used to refer to the Frontend -page of a Content Element. - -`crdate` The UNIX timestamp of the date the record was -created *(creation date)*. This date may differ from -the creation date of the domain object. - -`tstamp` The UNIX timestamp of the date the record was -changed the last time. Most often, this relates to the timestamp the Domain -Model was changed the last time. - -`deleted` When this fields' value differs from 0, TYPO3 -handles its corresponding record as if it was physically deleted. Thus it will show -off neither in the Backend nor in the Frontend. It can be restored by -either setting the field to 0 or more easily be dug out using the -system extension *Recycler*. Extbase will set this -field whenever a record is deleted in case that this field exists. Additionally, -it holds all the references to other records so that whole -*Aggregates* may be restored. - -`hidden` The record set won't show up in the Frontend if -this field's value differs from 0. - -`starttime` UNIX timestamp when the record first showed -up in the Frontend. Extbase uses this timestamp when it reads the record values from -the database not to create the domain objects before that -time. - -`endtime` UNIX timestamp when the record got "invisible" -in the Frontend (i.e., when its hidden value got non-zero). As well as with -the *starttime* field, Extbase uses this value when -it reads from the database. - -`cruser_id` The UID of the Backend user who created the -record. Currently, Extbase neither sets nor reads this value. Whenever a -domain object is created in the Frontend, this field is set to 0. - -`fe_group` A list of Frontend-Usergroups that can access -the recordset. The logged-in Frontend-User must at least belong to this -group. - -`sys_language_uid` The language's UID, which belongs to -this recordset. Languages may be created using the globe at the root of -the page tree. - -`l18n_parent` The UID of the translation source, i.e., the -recordset of the original language *(default)*. - -`l18n_diffsource` A serialized form of the translation -source. This is useful for showing the differences between the original -language and its translation in the Backend. - -`t3ver_oid`, `t3ver_wsid`, `t3ver_label`, `t3ver_state`, `t3ver_stage`, -`t3ver_origuid` Those -fields are used by TYPO3 for management of Versioning and -Workspaces. If they are not needed, they may be omitted. - -All fields except `uid` and `pid` are optional. -However, we highly recommend creating the fields `deleted`, -`hidden`, `fe_group`, `starttime`, and -`endtime` to enable proper access control. If the -domain objects are multi-lingual the fields `sys_language_uid`, -`l18n_parent`, `l18n_diffsource` are -essential. - -.. note:: - - More information about Localization and Multilingualism can be - found in Chapter 9. - -.. note:: - - The order of the field definitions is arbitrary. Nevertheless, it - is recommended to set the fields which are frequently inspected in a - SQL-tool like phpMyAdmin at the beginning since they are consequential - arranged at the left in the table view and show up without any annoying - scrolling. - -Every line in a table definition holds various statements. The -*field type* follows the field's name. In the following -case, the field `tstamp` takes an unsigned Integer number -(`unsigned`). The default value used if no value is -given when the record is created is the number 0 (`DEFAULT 0`). -The field value mustn't be NULL (`NOT NULL`), and a comma separates the field -definitions. `tstamp int(11) unsigned DEFAULT '0' NOT NULL,` - -.. note:: - - Note that in the case of the field `tstamp`, the field - definition is chosen somewhat awkwardly by TYPO3 since the value 0 - corresponds to the UNIX timestamp of the date 1.1.1970 00:00. It would - be better to use the value NULL for the meaning of 'undefined' instead - of 0. However, this inconsistency draws through the whole TYPO3 Core. - Thus, it is complicated to correct this weakness. - -SQL databases provide various field types. Which type is -chosen for persisting a Domain Property depends on the kind and length of -the value that is to be saved: Text strings are saved as -`char`, `varchar`, or `text`. Using -`char` and `varchar`, their length may be set in -round brackets. Whereas `char` may hold up to 255 characters -with a fixed size, `varchar` fields can hold up to 65.535 Bytes -as well as fields containing the type `text`. But record sets -cannot be grouped or sorted by fields with type `text`, and they -cannot have a standard value. Nonetheless, the type should still be -chosen if grouping, sorting, and setting a standard value can be resigned. -TYPO3 is usually used with the database engine MySQL, which -provides the developer with the field types `mediumtext` and -`longtext`. - -.. warning:: - Always spare memory, but, on the other side, don't be too penurious - with Strings since their values are simply cut-off when exceeding the - datatype range. This concludes with bugs and errors that are hard to - find. - -Integers are meant to have the field types `smallint`, -`int` and `bigint`. If working with a MySQL database, -additional fields `tinyint` and -`mediumint` are available. All those integer field types differ -only in the number range for which they can be used (see table -6-1). - -Floating-point types can be stored in fields with the type -`decimal` or `float`, where `decimal` -describes a fixed-size field type. E.g., a field defined with -`decimal(6,2)` takes a number with 6 digits before and 2 digits -after the comma, the standard value is (10,0). The keyword -`numeric` is a synonym for `decimal`. The type -`float` takes numbers from `-1.79E+308` to -`1.79E+308`, again, the range may be limited by a number (from -1 to 53) in round brackets. - -Besides the already defined field types, some other -types are, however, rather uncommon in the environment of TYPO3. -Examples for those uncommon types are `date` and -`datetime` for date values following the pattern -`YYYY-MM-DD` resp. `YYYY-MM-DD HH:MM:SS` or -`boolean` data types for true and false values. - -.. note:: - - As with field names of `char` and `varchar` - the `integer` types may take ranges as numbers in round - brackets upon their definition, e.g. `int(11)`. But in - contrast, they do NOT describe the count of digits or Bytes that can be - stored in that field. Instead, the number serves as a hint for SQL - management tools for correctly filling up the field type's column with - whitespaces. Thus, the fields defined with `int(11)` as well - as with `int(3)` can store the same value ranges from - `-21.474.838.648` to `+21.474.838.647`. It's still - useful to define `integer` data fields with their maximum - count of digits because this befriends the database computing complex - `JOIN`'s. Thus the rule of thumb is: Always use the maximum - possible value in round brackets when defining `integer` - fields (see table 6-1) plus one additional space for the sign value when - using signed numbers. - -Table 6-1 sums up all possible use-cases with their recommended data -types. - -*Table 6-1: Comparison of different field types* - -+------------------------------+--------------------+-------------------------------------------------+ -|What should be saved? |Field type |Field range | -| | | | -| | | | -| | | | -+------------------------------+--------------------+-------------------------------------------------+ -|Character strings, texts |`char(n)` |max. 255 Bytes ** | -+------------------------------+--------------------+-------------------------------------------------+ -|(names, addresses, product |`varchar(n)` |max. n Bytes (up to max. n = 65.553) | -|descriptions etc.; images that| | | -|are managed by TYPO3) |`text` |max. 65.553 Bytes | -| | | | -| |`mediumtext*` |max. 16.777.215 Bytes | -| | | | -| |`longtext*` |max. 4.294.967.295 Bytes | -+------------------------------+--------------------+-------------------------------------------------+ -|Integer types |`tinyint[(n)]` * |8 Bit | -| | | -128 to +127 (signed; n=4) | -|(item counts, ages etc.; in | | 0 to 255(unsigned; n=3) | -|TYPO3 as well as dates and | | | -|boolean properties) |`smallint[(n)]` |16 Bit | -| | | -32.768 to +32.767 (signed; n=6) | -| | | 0 to 65535 (unsigned; n=5) | -| | | | -| |`mediumint[(n)]` * |24 Bit | -| | | -8.388.608 to +8.388.607 (signed; n=9) | -| | | 0 to 16.777.215 (unsigned; n=8) | -| | | | -| |`int[(n)]` |32 Bit | -| | | -2.147.483.648 to +2.147.483.647 (signed; n=11) | -| | | 0 to 4.294.967.295 (unsigned; n=10) | -| | | | -| |`bigint[(n)]` |64 Bit | -| | | -9.223.372.036.854.775.808 to | -| | |+9.223.372.036.854.775.807 (signed; n=20) | -| | | 0 to 18.446.744.073.709.551.615 (unsigned; n=19)| -+------------------------------+--------------------+-------------------------------------------------+ -|Floating-point |`decimal(p[,s])` |(saved as string of characters) | -+------------------------------+--------------------+-------------------------------------------------+ -|(amounts of money, measurement|`float(p[,s])` |-1.79E+308 to +1.79E+308 (eventually limited | -|values etc.) | |by the precision) | -| | | | -+------------------------------+--------------------+-------------------------------------------------+ - - - - p = precision - - s = scale - - n = Number of Bytes resp. Number of spaces in a column (int) - - \* MySQL only - - ** The number of signs depends on the text-encoding and may - differ from the number of Bytes. E.g., Using text-encoding ISO-8859-1 - one Byte contains exactly one character, whereas, in UTF-8, one - character is saved in up to 3 Bytes (Multibyte Encoding). - -.. index:: Database; Object relationships - -Configure relationships between objects -======================================= - -There are many relations between the objects in our Domain that have -to be persisted in the database for being able to resolve them at a later -time. It depends on the type of relationship how they can be persisted and -Extbase distinguishes between several types as already defined in Chapter -5 "Implement Relationships between domain objects". As a reference to Chapter -5, find here a short summary of the types of relationships: - -*1:1-Relationship:* An offer has exactly one -range of time when it is valid (dateRange). - -*1:n-Relationship:* An organization may have -several contact persons whereas each contact person is in charge for -exactly one organization. - -*n:1-Relationship:* An organization has exactly -one administrator, but this administrator may be in charge for several -organizations. - -*m:n-Relationships:* An offer may be connected -with several categories and on the other hand, one certain category may be -attached to several offers. - -.. sidebar:: NULL or NOT NULL? - - All common Relational Database Management Systems (RDBMS) allow - `NULL` as a special value for a field. This usually means that this value - is kind of "not defined". However, be clear about the semantic - differences of the values `NULL`, `0` and - `"" ` (i.e., the NULL value, the number 0, and the empty - string). The difference gets clear with the value of the participation - fees of the `SjrOffers` example. If the field - `attendance_fee` contains the value `NULL`, then - the participation fee is not defined and NOT that the fee is 0 Euro. - However, in this concrete example, this may be due to the same - Frontend-output ("free of charge") but that has to be reasoned depending - on the use-case. - - * One cannot make calculations with `NULL` values. - The functions `AVG`, `SUM`, etc. ignore the - `NULL` value. - * One cannot do comparing instructions on `NULL` - values. For example, the comparison of `NULL = NULL` - always leads to `false` due to the vagueness of - `NULL`. Thus, it does not make sense to write a - statement like `uid = NULL,` and there is an own - operator introduced for that `IS` which leads to - expressions like `uid IS NULL`. However, Extbase - automatically figures out the right way for you. - * `NULL` values in queries like - `DISTINCT`, `ORDER BY` and `GROUP BY` are seen - the same way and are thus grouped together. - * Fields permitting `NULL` values take more memory - because it is harder to improve the database engine for those SQL - queries. - - A general rule of thumb is to avoid `NULL` - values as far as your Domain Semantic allows that. - -There are several techniques for persisting those relationships in a Relational Database: - -*Comma-separated list (Comma-separated values, -CSV):* In a field of the parent object's table, the UIDs of their -child objects are stored as comma-separated values. - -*Foreign Keys:* The UID of the child object's -table is stored in a field of the parent table or vice versa. - -*Intermediate Table:* For persisting the -relationships between two classes, a special table is -created - the Intermediate Table. The UID of the parent table, as well as -the UID of the child table, is stored as a data set in the -Intermediate Table. Additionally, information about -assorting, visibility, and access control can be stored. They -define the objects' relationships and not the objects -themselves. - -.. warning:: - Do not store data in the Intermediate Table that concern the - Domain. Though TYPO3 supports this (especially in combination with - *Inline Relational Record Editing (IRRE)* but this is - always a sign that further improvements can be made to your Domain - Model. Intermediate Tables are and should always be tools for storing - relationships and nothing else. - - Let's say you want to store a CD with its containing music tracks: - `CD -- m:n (Intermediate Table) -- Song`. The track number - may be stored in a field of the Intermediate Table. However, the - track should be stored as a separate domain object, and the connection be - realized as `CD -- 1:n -- Track -- n:1 -- Song`. - -Not all combinations of relationship types and their technical persistence are sane. -Table 6-2 lists all combinations that are **y** possible and useful, **(y)** technically -possible but rarely sensible, **no** either technically impossible or not -supported. - -+-----------------------+-----+-----+-----+-----+ -| |1:1 |1:n |n:1 |m:n | -+-----------------------+-----+-----+-----+-----+ -|Comma-separated list |\(y\)|\(y\)|n |\(y\)| -| | | | | | -+-----------------------+-----+-----+-----+-----+ -|Foreign Keys |y |y |y |n | -+-----------------------+-----+-----+-----+-----+ -|Intermediate Table |n |n |y |y | -+-----------------------+-----+-----+-----+-----+ - - Combination of relationship type and technical storage - -Thus, every type of relationship has its own recommended form of -persistence that will be explained subsequently. In case of a -1:1-relationship the UID of the child object will be saved in the Foreign -Key field of the parent object: - -.. code-block:: mysql - :caption: EXT:sjr_offers/ext_tables.sql - - CREATE TABLE tx_sjroffers_domain_model_offer( - # … - date_range int(11) DEFAULT '0' NOT NULL, - # … - ); - -The default values of '0' (or the `NULL` values if they were explicitly allowed) -stand for "The dateRange has not yet been assigned.". -Later on, Extbase computes the `DateRange`-object from the -uid. - -In a `1:n` relationship, there are two possibilities. -Either every `uid` value is stored as a comma-separated list in -the parent object field. Or every child object contains the parental -uid in a foreign key field. TYPO3 mostly uses the comma-separated list in its -Core. Still, we discourage that solution because of its drawbacks: -Comma-separated fields complicate the search and hinder the -indexation in the database. Furthermore, the creation and deletion of child -objects are complex and time-consuming. Thus, using comma-separated lists -for modeling relationships should only be used with database tables that -cannot be altered in their structure (e.g., external sources, the -TYPO3-Core). We highly recommend the latter method, which stores a Foreign -Key in the child object's table. In TYPO3, the parental object's -table holds a separate value for counting the corresponding -child objects' sum. Consecutively, we list the definition of the relationship -between the organization and its offers of the class -`\MyVendor\SjrOffers\Domain\Model\Organization`. This will later be -filled with instances of the class -`\MyVendor\SjrOffers\Domain\Model\Offer`. - -.. code-block:: mysql - :caption: EXT:sjr_offers/ext_tables.sql - - CREATE TABLE tx_sjroffers_domain_model_organization ( - # … - offers int(11) NOT NULL, - # … - ); - -The definition of the table -`tx_sjroffers_domain_model_offer` holds the field -`organization` as a Foreign Key. - -.. code-block:: mysql - :caption: EXT:sjr_offers/ext_tables.sql - - CREATE TABLE tx_sjroffers_domain_model_offer ( - # … - organization int(11) NOT NULL, - # … - ); - -.. note:: - - Extbase stores the relationship between `organization` - and the offer as a `1:1-relationship`. This can be taken as - advantage by adding the property `organization` to the class - `\MyVendor\SjrOffers\Domain\Model\Offer`. Consequently, it will be - filled with an instance of the class - `\MyVendor\SjrOffers\Domain\Model\Organization` and can therefore - be used as a backreference from the offer to its corresponding - organization. - -The `n:1` and the `1:n` are similar to -each other. It is just a matter of perspective. Concerning the persistence -of them, one is served with two possibilities. Either the relationship can -be stored as Foreign Key in the parent object or an Intermediate Table can -be used, which is described consecutively. We prefer the Foreign Key method -because it is easier to manage. - -The fourth kind of relationship, which is known by Extbase, is the -`m:n-relationship`. This uses an Intermediate Table for -persistence and stores the uid of the parent object as well as the uid of -the child object. The table definitions for a relationship between offer -and category are as follows: - -.. code-block:: mysql - :caption: EXT:sjr_offers/ext_tables.sql - - CREATE TABLE tx_sjroffers_domain_model_offer ( - # ... - categories int(11) NOT NULL, - # ... - ); - - CREATE TABLE tx_sjroffers_offer_category_mm ( - uid int(10) unsigned DEFAULT '0' NOT NULL auto_increment, - pid int(11) DEFAULT '0' NOT NULL, - - uid_local int(10) unsigned NOT NULL, - uid_foreign int(10) unsigned NOT NULL, - sorting int(10) unsigned NOT NULL, - sorting_foreign int(10) unsigned NOT NULL, - - tstamp int(10) unsigned NOT NULL, - crdate int(10) unsigned NOT NULL, - hidden tinyint(3) unsigned DEFAULT '0' NOT NULL, - - PRIMARY KEY (uid), - KEY parent (pid) - ); - -The table `tx_sjroffers_domain_model_offer` holds a field -`categories` as a counter (and as a counter-part to the -`categories` property). The Intermediate Table holds the field -`uid_local` that takes the `uid` of an offer as well -as a field `uid_foreign` for the uid of the category. Using the -values in the fields `sorting` and `sorting_foreign` -Extbase evaluates the order of the objects in the -`ObjectStorage`. While `sorting` orders the -categories from the perspective of an offer, `sorting_foreign` -evaluates the order of the offers from the perspective of a -category. - -.. note:: - - The name of the Intermediate Table can be chosen freely. However, - the following convention is recommended: - `tx_myext_leftobject_rightobject_mm`. - -For now, we have proper SQL definitions of the Domain's tables for -each kind of relationship. In the next step, we configure the -representation of the database tables and their interaction with the -Backend. diff --git a/Documentation/6-Persistence/2-configure-the-backends-inputforms.rst b/Documentation/6-Persistence/2-configure-the-backends-inputforms.rst deleted file mode 100644 index cfead7be..00000000 --- a/Documentation/6-Persistence/2-configure-the-backends-inputforms.rst +++ /dev/null @@ -1,755 +0,0 @@ -.. include:: /Includes.rst.txt -.. index:: - Table configuration array - TCA - Files: Configuration/TCA/*.php - -================================= -Configure the backend input forms -================================= - -In our sample application, the data of our extension should be editable in the -Backend by the editors of the youth club organization and - within certain -limitations - in the Frontend as well to provide functionalities for creation, -update and deletion of the organization's data. In this chapter, we first -configure the Backend's form inputs for easy access to the database's contents. -The forms that provide the management functionalities are stored in a -PHP-Array called `table configuration array (TCA)`. -The TCA is stored in a file with the database table name suffixed with `.php` in the directory :file:`Configuration/TCA/`. -Example: The TCA for the database table tx_sjroffers_domain_model_organization is therefore in the -file :file:`Configuration/TCA/tx_sjroffers_domain_model_organization.php`. - -.. note:: - - The configuration options that can be set in the TCA are very extensive and - a broad description of them would cause the book to burst at its - seams. However, each and every option is well documented in the - Online-documentation :doc:`TCA Reference ` - -First, you should dip into the top layer of the TCA hierarchy. The TCA -for table *tx_sjroffers_domain_model_organization* is in the -file :file:`Configuration/TCA/tx_sjroffers_domain_model_organization.php` and has this structure: - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - [ - // … - ], - 'columns' => [ - // … - ], - 'types' => [ - // … - ], - ]; - -The structure for the other tables like tx_sjroffers_domain_model_offer and tx_sjroffers_domain_model_person are the same. - -The returned arrays will be added to one big array :php:`$GLOBALS['TCA']`. You can debug the configuration -for the table `tx_sjroffers_domain_model_organization` in the TYPO3 backend module `System -> Configuration -> $GLOBALS['TCA'] (Table configuration array) -> tx_sjroffers_domain_model_organization` - -The associative array that is returned contains all information of all the tables of the TYPO3 -instance. Thus, we use the key `tx_sjroffers_domain_model_organization,` and as -the value, we use another nested Array that holds the configurations of the -corresponding table. Then again, this array is separated into several parts -with names that are the keys of the nested array. - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - [ - // … - ], - 'interface' => [ - // … - ], - 'types' => [ - // … - ], - 'palettes' => [ - // … - ], - 'columns' => [ - 'first_fieldname' => [ - // … - ], - 'second_fieldname' => [ - // … - ], - ], - ]; - - -Below, you find the names of the parts and their meaning. - - -.. index:: TCA; ctrl - -`ctrl` ------- - -This part contains configuration options that are used in the scope of the -table. This covers the name of the table in the Backend, which table fields -contain which metadata and the behavior of the table on creation and movement -of its row sets. Metadata cover information about Visibility and Access -Control (e.g. `disabled`, `hidden`, `starttime`, `endtime`, -`fe_group`), data about the history of changes (e.g. `versioningWS`, -`crdate`, `tstamp` as well as data for the localization of data sets (e.g. -`languageField`). - - -.. index:: TCA; interface - -`interface` ------------ - -This part contains information about the representation of the table data in the -Backend's List Module. The key `showRecordFieldList` contains a -comma-separated list of field values that will be shown in the info -dialogue of the table. This dialogue may be reached through a right-click on the -icon of the row set through the `Info` option. The option -`maxDBListItems` allows you to set how many row sets will be shown in the List Module -without switching to the detail view of the database table. Then again, the -number of row sets shown on a page in this perspective may be set via -`maxSingleDBListItems`. If the option `always_description` is set to *true*, -the corresponding help texts always show up. - - -.. index:: TCA; types - -`types` -------- - -This section defines the appearance of the Input Form for the creation and update of -a row set. You can define several layout types by listing several elements in the -array `types`. The key of all those elements is their type (usually a number) -and their value is another nested array which itself usually contains one -element with `showItem` as key and a list of comma-separated field names that -should emerge at the Input Form. An example of the table -`tx_sjroffers_domain_model_organization` is: - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'types' => [ - '0' => ['showitem' => 'hidden,status,name,address;;1;;description,contacts,offers,administrator'], - ], - -Even though the behavior and the appearance of the table fields are configured in -the section `columns`, it is required to list them explicitly here so that they show -up in the input form. This prevents trouble when commenting out -or moving code that is already configured, and a corresponding field should -just be hidden, or the overall order of the Input Form's table fields should be -changed. - -The behavior and the appearance of a field may be altered through several -additional parameters - as well as with the field `address`. The notion -convention of those additional params may seem a bit unfamiliar since they are -appended behind the field name and separated through a semi-colon. At the first -position, there is the field name; at the second, an alternative name of the field name; -third place follows the number of the palette (refer to the next book -section); the fourth position holds extensive options which are separated -through colons, and the last place contains information about the appearance -(e.g., color and structure). The information at the fourth place allows the use of -the *Rich Text Editor*. For a full list of the options, refer to the already -mentioned TYPO3-Online documentation for the :doc:`TYPO3-Core API `. - - -.. index:: TCA; palettes - -`palettes` ----------- - -Palettes are used to collect occasionally used fields and show them upon -demand. The Backend user has to choose the Extended View in the -Backend's List module to view these. Palettes are connected to a durable visible field. An -example from the table `tx_sjroffers_domain_model_organization` is: - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'palettes' => [ - '1' => ['showitem' => 'telephone_number,telefax_number,url,email_address'], - ], - -The structure is the same as in the section *types* where `address;;1;;` -refers to the palette with the number 1. - - -.. index:: TCA; columns - -`columns` ---------- - -This array contains information about the behavior and the appearance in the -Input Form of every table field. The field name is the key, and, again, the value -is a nested array holding the field's corresponding configuration. The field -configuration for the input of the name of an organization would be as follows: - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'name' => [ - 'exclude' => false, - 'label' => 'LLL:EXT:sjr_offers/Resources/Private/Language/locallang_db.xml:tx_sjroffers_domain_model_organization.name', - 'config' => [ - 'type' => 'input', - 'size' => 20, - 'eval' => 'trim,required', - 'max' => 256 - ], - ], - -The field name is *name*. First, we define some options that are independent -of the field's type. This contains foremostly the field label (*label*), the -conditions for the visibility of the field (`exclude`, `displayCond`) as -well as information for its localization (`l10n_mode`, `l10n_cat`). The -field name is, in our case, localized and will be taken from a language file -(head to Ch. 9). - -The array connected to `config` contains the field type and its corresponding -configuration. TYPO3 provides a great range of pre-defined field types, e.g. -text fields, date fields, or selection fields. Each and every type has its own -presentation and procession options. Consecutively, you will find a list of all -the field types with their usual configuration: - - -.. index:: Field types; input - -Field type "input" -================== - -The *input* field type accepts a one-line character string like names and -telephone numbers. The configuration of a name field (see Fig. 6-1) looks as -follows: - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'name' => [ - 'label' => 'Organization\'s name', - 'config' => [ - 'type' => 'input', - 'size' => 20, - 'eval' => 'trim,required', - 'max' => 256 - ], - ], - -The given string will be truncated to 256 characters (`'max' => 256`), ending -spaces will be dropped (`trim`), and the status of this field being empty will be -prevented (`required`). - -.. note:: - - **Important:** When a field is defined as required, the Domain Model must have the - annotation `@NotEmpty` for the validator. - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-1.png - :align: center - - Figure 6-1: An example of the field type "input" used as a name field. - -The field type `input` may be used for date and time inputs: - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'minimum_value' => [ - 'label' => 'valid since', - 'config' => [ - 'type' => 'input', - 'size' => 8, - 'checkbox' => '', - 'eval' => 'date' - ], - ], - -The value then will be tested for being given in an appropriate date format. -Simultaneously, this leads to the rendering of a collapsible calendar page with -an icon right to the input field, which is shown in Fig. 6-2: - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-2.png - :align: center - - Figure 6-2: An example of the field type "input" used as a date field. - - -.. index:: Field types; text - -Field type "text" -================= - -The `text` field type may contain multi-line formatted or unformatted texts -, e.g., product descriptions, addresses, or news items. The indication of the lines -(`rows`) and the columns (`cols`) specifies the area of the text input field. - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'address' => [ - 'label' => 'Address:', - 'config' => [ - 'type' => 'text', - 'cols' => 20, - 'rows' => 3 - ], - ], - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-3.png - :align: center - - Figure 6-3: An example for the field type "text". - - -.. index:: Field types; check - -Field type "check" -================== - -The field type `check` allows the definition of a single option (see Fig. 6-4) - e.g., you can define whether a rowset should be hidden or not. - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'hidden' => [ - 'label' => 'Hide:', - 'config' => [ - 'type' => 'check' - ], - ], - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-4.png - :align: center - - Figure 6-4: An example for the field type "check" for a single option. - -Several related options that can be individually selected can be grouped into a -field (see Fig. 6-5). This may be helpful, e.g., for a selection of valid weekdays -or recommended training levels of a certain exercise. - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'level' => [ - 'exclude' => true, - 'label' => 'Property for', - 'config' => [ - 'type' => 'check', - 'eval' => 'required,unique', - 'cols' => 5, - 'default' => 31, - 'items' => [ - ['Level 1',''], - ['Level 2',''], - ['Level 3',''], - ['Level 4',''], - ['Level 5',''], - ], - ], - ], - -.. - -The value that is written to the database is of type Integer. This will be -computed by the bitwise addition of the checkboxes states (which can be 1 or 0). The -first element (Level 1) is the least significant bit (= 2^0 = 1). The second -element is one level above (= 2^1 = 2), the third element will then be (= 2^2 = -4) etc. The selection in the following Figure (see Fig. 6-5) would lead to the -following Bit-Pattern (= binary-written number): 00101. This binary number is -equivalent to the integer value 5. - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-5.png - :align: center - - Figure 6-5: An example of the field type "check" for several options that are grouped together. - - -.. index:: Field types; radio - -Field type "radio" -================== - -The field type radio is for choosing one unique value for a given property (see -Fig. 6-6), e.g., the sex of a person or the color of a product. - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'gender' => [ - 'label' => 'Sex', - 'config' => [ - 'type' => 'radio', - 'default' => 'm', - 'items' => [ - ['male', 'm'], - ['female', 'f'], - ], - ], - ], - -The options (*items*) are given in an array and each option is an array itself -containing the label and the key used to persist the selected option in the -database. - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-6.png - :align: center - - Figure 6-6: An example of the field type "radio". - - -.. index:: Field types; select - -Field type "select" -=================== - -The field type "select" provides a space-saving way to render multiple values -(see Fig. 6-7). Examples could be a member status, a product color, or a region. - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'status' => [ - 'exclude' => false, - 'label' => 'Status', - 'config' => [ - 'type' => 'select', - 'foreign_table' => 'tx_sjroffers_domain_model_status', - 'maxitems' => 1 - ], - ], - -The options are taken from another database table (*foreign_table*) and by -setting *maxitems* to 1 (which is standard), the selection box will be limited to -exactly one showed item. - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-7.png - :align: center - - Figure 6-7: An example for the field type "select" showing a selection box. - -The type `select` may also be used to select a whole subset of values. This is -used for categories, tags, or contact persons (see Fig. 6-8). - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'categories' => [ - 'exclude' => true, - 'label' => 'Categories', - 'config' => [ - 'type' => 'select', - 'renderType' => 'selectMultipleSideBySide', - 'size' => 10, - 'minitems' => 0, - 'maxitems' => 9999, - 'autoSizeMax' => 5, - 'multiple' => 0, - 'foreign_table' => 'tx_sjroffers_domain_model_category', - 'MM' => 'tx_sjroffers_offer_category_mm' - ], - ], - -Again, this takes the options of another table, but it holds the references in a -temporary table *tx_sjroffers_offer_category_mm*. - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-8.png - :align: center - - Figure 6-8: An example for the field type "select". - - -.. index:: Field types; group - -Field type "group" -================== - -The "group" field type is very flexible in its use. It can be used to manage -references to resources of the filesystem or rowsets of a database (see Fig. 6-9). - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'pages' => [ - 'label' => 'Pages', - 'config' => [ - 'type' => 'group', - 'internal_type' => 'db', - 'allowed' => 'pages', - 'size' => 3, - 'maxitems' => 50, - 'minitems' => 0 - ] - ], - -The combination of `type` and `internal_type` specifies the field's type. - - -.. index:: Field types; none - -Field type "none" -================= - -Fields of this type show the raw data values which cannot be edited (see Fig. 6-10). - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'date' => [ - 'label' => 'Datum (Timestamp)', - 'config' => [ - 'type' => 'none' - ], - ], - -In contrast to the date field with the type `input`, there is no evaluation as -with `'eval' => 'date'`. The timestamp which is set in the database will be -shown as a raw number. - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-10.png - :align: center - - Figure 6-10: An example for the field type "none" for a date field. - - -.. index:: Field types; passthrough - -Field type "passthrough" -======================== - -The field type "passthrough" is for data that is processed internally but cannot -be edited or viewed in the form. An example of that would be information to -references (foreign keys). - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'organization' => [ - 'config' => [ - 'type' => 'passthrough' - ], - ], - -This field configuration in the database table -`tx_sjroffers_domain_model_offer` has the effect that the property -`organization` of the `Offer`-object will be filled with the correct object. - - - -.. index:: Field types; user - -Field type "user" -================= - -A user generates free definable form fields that can be processed by any PHP -function. For further information, refer to the documentation which is available -online and to the :doc:`TYPO3-Core API `. - - -.. index:: Field types; flex - -Field type "flex" -================= - -The field type "flex" manages complex inline form fields (*FlexForms*). The -form data will be saved as an XML data structure in the database fields. -Extbase uses FlexForms for persisting plugin configuration but not to save -domain data. If your plugin data are rather complex, we encourage you to -design your own backend module for them (refer to Ch. 10). - - -.. index:: - Field types; inline - IRRE - Inline relational record editing - -Field type "inline" -=================== - -The field type "inline" is for saving complex Aggregates of the Domain (see Fig. -6-11). The basis of this field type is the so-called *Inline Relational Record -Editing (IRRE)* which powers the creation, update, and deletion of Domain-objects -of whole Aggregates in a single Input Form. Without *IRRE* the Domain-objects -must be edited and connected each by itself, which would require an intermediate -save. This technique is a comfortable tool for managing complex Aggregates. All -the possibilities provided by IRRE are well documented, refer to :ref:`the TYPO3 -TCA Reference `. - - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - - 'offers' => [ - 'label' => 'Offers', - 'config' => [ - 'type' => 'inline', - 'foreign_table' => 'tx_sjroffers_domain_model_offer', - 'foreign_field' => 'organization', - 'maxitems' => 9999 - ], - ], - -The configuration is almost identical to the field type "select". However, there -are several more possibilities for the configuration of the management and the -representation of the connected objects. - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-11.png - :align: center - - Figure 6-11: An example for the field type "inline". - -Extbase supports the most important aspects of *IRRE* with only one exception: -*IRRE* allows a temporary table of an `m:n-relationship` to be enhanced by -additional fields that can hold Domain data. An example: Assume that we want to -connect a CD to it's containing music tracks, whereas a CD can contain multiple -tracks and one track can be present on several CD's. Thus, we can derive the -following temporary table: - -`CD --1:n-- Temporary-Table --n:1-- Title` - - -The corresponding *IRRE*-Configuration looks as follows: - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_offer.php - - 'titles' => [ - 'label' => 'Track Title', - 'config' => [ - 'type' => 'inline', - 'foreign_table' => 'tx_myext_cd_title_mm', - 'foreign_field' => 'uid_local', - 'foreign_selector' => 'uid_foreign' - ], - ], - -The *IRRE*-Tutorial describes this configuration as "state-of-the-art" for -m:n-relationships. The option `foreign_selector` leads to a selection box for -the music titles. Currently, *IRRE* only supports this option for -m:n-relationships. - -Every music track on the CD is given a unique track number. However, the track -number is neither a property of the CD nor that of a track. It semantically -corresponds to the relationship *between* them. Thus, IRRE provides the option -to persist them within the temporary table, and this can always be modeled into -the Domain model, which gets the following structure: `CD --1:n-- Track --n:1--Title`. - -Let's change the configuration of the table `tx_myext_domain_model_track` to a -simple 1:n-relationship with `cd` as a foreign key. - -.. code-block:: php - :caption: EXT:my_ext/Configuration/TCA/tx_myext_domain_model_track.php - - 'tracks' => [ - 'label' => 'Track', - 'config' => [ - 'type' => 'inline', - 'foreign_table' => 'tx_myext_domain_model_track', - 'foreign_field' => 'cd' - ], - ], - -However, Extbase does not support the persistence of additional Domain data in -the temporary table because the corresponding Domain object does not exist. -Nevertheless, the online documentation of the :doc:`TYPO3-Core API ` describes the -second, more correct option for configuring m:n-relationships within IRRE. It -depends on a plain temporary table. The following example shows off the -configuration of products with their according categories: - -.. code-block:: php - :caption: EXT:my_ext/Configuration/TCA/tx_myext_domain_model_track.php - - 'categories' => [ - 'label' => 'Categories', - 'config' => [ - 'type' => 'inline', - 'foreign_table' => 'tx_myext_domain_model_category', - 'MM' => 'tx_myext_product_category_mm' - ], - ], - -This second option deserves some additional kudos because it does not need a -TCA-configuration for the temporary table *tx_myext_product_category_mm*. -You don't need to show up or edit the whole table or parts of it in the Backend - -the SQL definition is sufficient. - -Those are the summarized configuration possibilities within the TCA. As you see, -the huge count of options can be overwhelming for the novice. But in the future, -they can be auto-generated by the Extension Builder (refer to Ch. 10). - -.. index:: TCA; Storage - -As already mentioned, the TCA is stored in a file with the database table name as filename suffixed with `.php` in the -directory :file:`Configuration/TCA/` - -.. code-block:: php - :caption: EXT:sjr_offers/Configuration/TCA/tx_sjroffers_domain_model_organization.php - :name: tca-tx-sjroffers-domain-model-organization-php - - [ - 'title' => 'LLL:EXT:sjr_offers/Resources/Private/Language/locallang_db.xlf:tx_sjroffers_domain_model_organization', - 'label' => 'name', - 'tstamp' => 'tstamp', - 'crdate' => 'crdate', - 'languageField' => 'sys_language_uid', - 'transOrigPointerField' => 'l18n_parent', - 'transOrigDiffSourceField' => 'l18n_diffsource', - 'prependAtCopy' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.prependAtCopy', - 'copyAfterDuplFields' => 'sys_language_uid', - 'useColumnsForDefaultValues' => 'sys_language_uid', - 'delete' => 'deleted', - 'enablecolumns' => [ - 'disabled' => 'hidden' - ], - 'iconfile' => 'EXT:sjr_offers/Resources/Public/Icons/Icon_tx_sjroffers_domain_model_organization.svg' - ], - 'interface' => [ - 'showRecordFieldList' => 'status,name,address,telephone_number,telefax_number,url,email_address,description,contacts,offers,administrator' - ], - 'types' => [ - '0' => ['hidden,status,name,address;;1;;,description, contacts,offers,administrator'] - ], - 'palettes' => [ - '1' => ['showitem' => 'telephone_number,telefax_number,url,email_address'] - ], - 'columns' => [ - 'sys_language_uid' => […], - 'l18n_parent' => […], - 'l18n_diffsource' => […], - 'hidden' => […], - 'status' => […], - 'name' => […], - 'address' => […], - 'telephone_number' => […], - 'telefax_number' => […], - 'url' => […], - 'email_address' => […], - 'description' => […], - 'contacts' => […], - 'offers' => […], - 'administrator' => […], - ], - ]; - -The tables of all the Domain objects are defined like this. - -Now we can create a directory (*SysDirectory*), which will contain all the data -sets. Let's create our first organization (see Fig. 6-12). - -.. figure:: /Images/ManualScreenshots/6-Persistence/figure-6-12.png - :align: center - - Figure 6-12: The input form for creating an organization with all its offers. - -Now you can set up the whole data structure. In our project, this allows the -offer-provider to set up some example data, and thus we could do some early -integration tests. However, we can not access the given data because we still -miss the repositories that will be defined in the following section. diff --git a/Documentation/6-Persistence/2a-creating-the-repositories.rst b/Documentation/6-Persistence/2a-creating-the-repositories.rst deleted file mode 100644 index f839fb46..00000000 --- a/Documentation/6-Persistence/2a-creating-the-repositories.rst +++ /dev/null @@ -1,342 +0,0 @@ -.. include:: /Includes.rst.txt -.. index:: Repositories; Creation - -========================= -Creating the repositories -========================= - -Repositories serve with -capabilities to save and reaccess objects. We set up such a repository -object for every Aggregate-Root object which is, then again, used for accessing -all the Aggregate-Root's corresponding objects. In our concrete example -:php:`\MyVendor\SjrOffers\Domain\Model\Organization` is such an Aggregate-Root object. The -repository's class name is derived from the class name of the Aggregate-Root -object concatenated with the suffix *repository*. The repository needs to extend -the class :php:`\TYPO3\CMS\Extbase\Persistence\Repository`. The class file :php:`\MyVendor\ -SjrOffers\Domain\Repository\OrganizationRepository` will be saved in the -directory :file:`EXT:sjr_offers/Classes/Domain/Repository/`. Thus the directory -*repository* is on the same hierarchy-level as the directory *Model*. In our -case, the class body remains empty because all the important functionalities are -already generically implemented in the super-class -:php:`\TYPO3\CMS\Extbase\Persistence\Repository`. - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OrganizationRepository.php - - QueryInterface::ORDER_ASCENDING - ); - - } - -Fields can be ordered reversely by setting the value of the array entry -to :php:`QueryInterface::ORDER_DESCENDING`. - - -.. _extbase_repository_default_query_settings: - -Default query settings ----------------------- - -The default query settings of a repository are stored in the protected variable -:php:`$defaultQuerySettings` as an object of type -:php:`QuerySettingsInterface`. This variable is usually called by setting -it via the public function :php:`setDefaultQuerySettings()` from the function -:php:`initializeObject()`. - -Here is an example: - -.. code-block:: php - :caption: EXT:blog_example/Classes/Domain/Repository/ExampleRepository.php - - use TYPO3\CMS\Core\Utility\GeneralUtility; - use TYPO3\CMS\Extbase\Persistence\Repository; - use TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings; - - class ExampleRepository extends Repository { - - // Example for repository wide settings - public function initializeObject() { - /** @var Typo3QuerySettings $querySettings */ - $querySettings = GeneralUtility::makeInstance(Typo3QuerySettings::class); - - // don't add the pid constraint - $querySettings->setRespectStoragePage(false); - // set the storagePids to respect - $querySettings->setStoragePageIds(array(1, 26, 989)); - - // define the enablecolumn fields to be ignored, true ignores all of them - $querySettings->setIgnoreEnableFields(TRUE); - - // define single fields to be ignored - $querySettings->setEnableFieldsToBeIgnored(array('disabled','starttime')); - - // add deleted rows to the result - $querySettings->setIncludeDeleted(TRUE); - - // don't add sys_language_uid constraint - $querySettings->setRespectSysLanguage(FALSE); - - $this->setDefaultQuerySettings($querySettings); - } - } - - -.. _procedure_to_fetch_objects: - -Fetch Extbase objects -===================== - -Generally, there are three cases which need to be distinguished: Persisting a -newly created object, reaccessing an existing object and updating the properties -of an existing object. When creating a new object, Extbase determines the -destination pages in the following rule hierarchy: - - - -.. todo Check if the work for "Ausgangspunkt" is used as in Ch. 4 - - -* If, as already described in Chapter 4, the option *source* is checked, then the objects will be searched in the corresponding pages -* If the TypoScript-Setup of the page contains the definition of :typoscript:`plugin.tx_*extensionname*.persistence.storagePid` with a comma-separated list of PIDs then those pages will be consulted. -* If the TypoScript-Setup of the page contains the definition of :typoscript:`config.tx_extbase.persistence.storagePid` with a comma-separated list of PIDs then those pages will be consulted. -* If none of the cases from above applies, then the root page will be consulted for the objects. - -When new Domain objects are inserted, then the procedure will be as follows: - -* If there's a TypoScript setup at :typoscript:`plugin.tx_extensionname.persistence.classes.*FullClassName*.newRecordStoragePid` with a single page value, then this is gonna be used. -* If there's a TypoScript setup at :typoscript:`config. tx_extbase.persistence.classes.*FullClassName*.newRecordStoragePid` with a single page value, then this is gonna be used. -* If none of the cases above apply, then the object will be inserted at the first item in the list of search pages. So to say, in the end, the root page (the one with the globe) is gonna be used for insertion. - - -When updating the Domain objects, their PID is not changed. However, you can -implement the property `pid` in your domain object with its corresponding set- -and get-methods. A domain object may be moved from one page to another by -setting a new `pid`. - -.. note:: - - The most occurring mistake for seemingly empty repositories is a misconfigured - *Storage-PID*. Thus, you should first evaluate the template module, whether - it is set correctly. - - -Besides the option for setting the Page UID, two other possibilities for configuring the Persistence Layer exist: -*enableAutomaticCacheClearing* and *updateReferenceIndex*. The option -:typoscript:`config.tx_extbase.persistence.enableAutomaticCacheClearing = 1` within the -TypoScript setup leads to a deletion of the cache whenever the data is -rewritten. This option is normally activated. - - -.. sidebar:: TYPO3's Page Tree - - In TYPO3, each content element and dataset which should be rendered in the - backend corresponds to a certain page. Technically, a page is nothing more - than a node element or a leaf in the virtual page tree. Every page is - associated with a unique page id (PID). Some of the pages are reachable via - a URL and TYPO3 renders and delivers them (usually in HTML). For example, - the URL :samp:`https://example.org/index.php?id=123` requests the page with the - PID 123. In this case, the term page has the meaning of being a web page. But - there are other cases, e.g., a directory (*SysFolder*) or a separator, which - are used to save data in a clear and structured way. A special already - existing PID is 0, which is used to refer to the root page (the one with the - shiny globe). - -.. note:: - - Usually, data sets will be saved into folders in the page tree though the - pages using those data sets will be somewhere else. If their cache should be - cleared as well, then you should set up their PIDs in the field *TSconfig* of - the page's preferences of the directory. For example, our offers will be - shown on the pages with the PIDs 23 and 26 (let's say for a `SingleView` and a - `ListView`). Then we will configure the variable - `TCEMAIN.clearCacheCmd=23,26` - in the page preferences of the SysFolder. Then the cache of these - pages will be cleared as well, and changes of an offer will show up - immediately. - -Internally, TYPO3 manages an index of all relationships between two data sets, the -so-called *RefIndex*. Due to this index, it's possible to show the number of -associated data sets in the list module's column *[Ref.]*. By clicking on the -number, you get further information about the incoming and outgoing references -of the dataset. This index is automatically updated when any data sets are -edited. The configuration `config.tx_extbase.persistence.updateReferenceIndex = 1` -causes an immediate update when data sets are edited in the Frontend though it is -normally deactivated due to its huge effects on performance. - -Before calling a repository's methods, they need to be instantiated -via :ref:`dependency injection` first. - -.. code-block:: php - :caption: Dependency injection example from chapter 2 - :name: - - use FriendsOfTYPO3\BlogExample\Domain\Repository\BlogRepository; - - private BlogRepository $blogRepository; - - /** - * Inject the product repository - */ - public function injectProductRepository(BlogRepository $blogRepository) - { - $this->blogRepository = $blogRepository; - } - - -.. warning:: - - repositories are *Singletons* therefore, there may only exist one instance of - each class at one point in time during script-execution. If a new instance is requested, - the system will check whether an instance of the requested object exists already. In that case, - the system will return the existing object instead of creating a new one. This is - ensured by using the dependency injection. Thus, never ever use the PHP syntax - keyword :php:`new` for creating a repository object because the new objects - will not automatically be persisted. - -Now you know all the basic tools for persisting and recovering your -objects. Extbase offers a lot more sophisticated functionalities for special -needs because it happens quite frequently that the standard methods of saving -and seeking data in a repository are not sufficient for the individual case. -Thus Extbase lets you define individual requests without losing the existing -abstractions of the existing persistence backend. Additionally, Extbase lets -you use "foreign" data sources, which are most often data tables of the same -database. diff --git a/Documentation/6-Persistence/3-implement-individual-database-queries.rst b/Documentation/6-Persistence/3-implement-individual-database-queries.rst deleted file mode 100644 index f20449e7..00000000 --- a/Documentation/6-Persistence/3-implement-individual-database-queries.rst +++ /dev/null @@ -1,686 +0,0 @@ -.. include:: /Includes.rst.txt -.. index:: Queries; Custom -.. _individual_database_queries: - -=========================== -Individual database queries -=========================== - -The previous descriptions about generic methods of queries to a repository are -sufficient for simple use-cases. However, there are many cases where they are -not adequate and require more flexible solutions. On the requirements list of -our application is the functionality to print a list of all the offers. In -natural language, this would sound as follows: - -* "Find all the offers for a certain region." -* "Find all the offers corresponding to a certain category." -* "Find all the offers containing a certain word." -* "Find offers that are associated to a selected set of organizations." - -There are principally two ways of implementing such methods. On the one hand, you -could request all the offers from the Backend and filter them manually. This is -flexible and easy to implement. On the other hand, you could write a request -matching your criteria exactly and execute it. Contrary to the first case, -this method would only build the objects that are really needed, which -positively affects your application's performance. - - -.. note:: - - You may start developing your application using the first method and then, - seeing your application growing, veer to the second method. Luckily, all - the changes are encapsulated in the repository, so you don't have - to change any code of the Persistence Backend. - - -You can use Extbase's *Query*-object for implementing individual queries by -giving it all the essential information needed for a qualified request to the -database backend. This information contains: - -* The request's class (*Type*) to which the request applies. -* An (optional) *Constraint* which restricts the result set. -* (Optional) Parameters that configure a section of the result set by a *limit* or an *offset*. -* (Optional) Parameters concerning the *Orderings* of the result set. - -Within a repository, you can create a Query object by using the command -:php:`$this->createQuery().` The Query object is already customized to the class -which is managed by the repository. Thus, the result set only consists of -objects of that class, i.e. it consists of Offer objects within the -:php:`OfferRepository`. After giving all the needed information to the Query object -(detailed information will be given later on), you execute the request by using -:php:`execute()` which returns a sorted Array with the properly instantiated -objects (or a via limit and offset customized section of it). For example, the -generic repository method :php:`findAll()` looks as follows: - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OfferRepository.php - - /** - * Returns all objects of this repository. - * - * @return QueryResultInterface|array - * @api - */ - public function findAll() - { - return $this->createQuery()->execute(); - } - -More repository search methods are available: - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OfferRepository.php - - /** - * Finds an object matching the given unique id. - * - * @param int $uid The unique id of the object to find - * @return object The matching object if found, otherwise NULL - * @api - */ - public function findByUid(int $uid) - { - return $this->findByIdentifier($uid); - } - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OfferRepository.php - - /** - * Finds an object matching the given identifier. - * - * @param int $identifier The identifier of the object to find - * @return object The matching object if found, otherwise NULL - * @api - */ - public function findByIdentifier(int $identifier) - { - return $this->persistenceManager->getObjectByIdentifier($identifier, $this->objectType); - } - -You must set the `storagePid` to the allowed pages before a query finds any records. -By default, a query only searches on the root page with `id=0`. - -TypoScript example of an extension for the page id's 12 and 22 - -.. code-block:: typoscript - :caption: EXT:blog_example/Configuration/TypoScript/setup.typoscript - - plugin.tx_myextension { - persistence { - storagePid = 12,22 - } - } - - -In the first simple use-case, we don't apply any constraining parameter to the Query -object. However, we have to define such a parameter to implement the first -specified request, "Find all the offers for a certain region". Thus, the -corresponding method looks as follows: - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OfferRepository.php - - public function findInRegion(\MyVendor\SjrOffers\Domain\Model\Region $region) - { - $query = $this->createQuery(); - $query->matching($query->contains('regions', $region)); - return $query->execute(); - } - -Using the method ``matching()`` we give the Query the following condition: The -property *regions* of the object *Offer* (which is managed by the repository) -should contain the region that is referenced by the variable ``$region``. The -method ``contains()`` returns a *Constraint* object. The Query object has some -other methods each of which returns a *Constraint* object. Those methods may be -roughly split into two groups: Comparing operations and Boolean operations. -The first group leads to a comparison between the value of a given property -and another operand. The latter mentioned operations connect two conditions to -one condition by the rules of Boolean Algebra and may -respectively negate a result. The following Comparing operations are acceptable: - -.. code-block:: none - :caption: Examples of different comparing operations - - equals($propertyName, $operand, $caseSensitive = TRUE) - in($propertyName, $operand) - contains($propertyName, $operand) - like($propertyName, $operand) - lessThan($propertyName, $operand) - lessThanOrEqual($propertyName, $operand) - greaterThan($propertyName, $operand) - greaterThanOrEqual($propertyName, $operand) - between($propertyName, $operandLower, $operandUpper) // inclusive comparison - -The method ``equals()`` executes a simple comparison between the property's -value and the operand, which may be a simple PHP data type or a Domain object. - -Contrarily, the methods ``in()`` and ``contains()`` accept multi-value data types -as arguments (e.g. Array, ObjectStorage). As ``in()`` checks if a single-valued -property exists in a multi-value operand, the latter method ``contains()`` -checks if a multi-valued property contains a single-valued operand. The opposite -of the introduced method ``findInRegion()`` is ``findOfferedBy()`` which accepts -a multi-valued operand (``$organizations``). - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OfferRepository.php - - public function findOfferedBy(array $organizations) - { - $query = $this->createQuery(); - $query->matching($query->in('organization', $organizations)); - return $query->execute(); - } - -.. note:: - - If you pass an empty multi-valued property value or an empty - multi-valued operand (e.g., an empty Array) to ``in()``, you always get *false* - as the return value. Thus you have to prove if the operand - ``$organizations`` of the method call ``$query->in('organization', - $organizations)`` contains sane values or if it is just an empty Array first. This - is dependent on your domain logic. In the last example, the method - ``findOfferedBy()`` would return an empty set of values. - - -It's possible to use comparison operators that are reaching deep into the object tree -hierarchy. Let's assume you want to filter the organizations by whether -they have offers for youngsters older than 16. You may define the request in the -``OrganizationRepository`` as follows: - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OrganizationRepository.php - - $query->lessThanOrEqual('offers.ageRange.minimalValue', 16) - -Extbase solves the path ``offers.ageRange.minimalValue`` by seeking every -organization having offers whose age values have a minimum of less than or -equal to 16. Assuming that a Relational Database System is used in the Persistence -Backend, this is internally solved by a so-called *INNER JOIN*. This feature covers all relational -types (1:1, 1:n, m:n) and all comparison operators. - -Besides comparison operators, the ``Query`` object supports Boolean -Operators such as: - -.. code-block:: none - :caption: Examples of different boolean operators - - logicalAnd($constraint1, $constraint2) - logicalOr($constraint1, $constraint2) - logicalNot($constraint) - -The methods above return a ``Constraint`` object. The resulting ``Constraint`` -object of ``logicalAnd()`` is true if both given parameters ``$constraint1`` and -``$constraint2`` are true. It's sufficient when using ``logicalOr()`` to -be true if only one of the given parameters is true. Both methods -accept an Array of constraints. Last but not least, the function -``logicalNot()`` inverts the given ``$constraint`` to its opposite, i.e. *true* -yields *false* and *false* yields *true*. Given this information, you can create -complex queries such as: - -.. code-block:: php - :caption: EXT:my_extension/Classes/Domain/Repository/MyRepository.php - - use MyVendor\SjrOffers\Domain\Model\Organization; - use MyVendor\SjrOffers\Domain\Model\Region; - - public function findMatchingOrganizationAndRegion(Organization $organization, Region $region) - { - $query = $this->createQuery(); - $query->matching( - $query->logicalAnd( - $query->equals('organization', $organization), - $query->contains('regions', $region) - ) - ); - return $query->execute(); - } - -The method :php:`findMatchingOrganizationAndRegion()` returns those offers that -match both the given organization and the given region. - -For our example extension, we have the complex specification to find all offers -that comply with the user's requirements. The requirements are given -via information about the age, the organization, the city district, the category, -and a freely defined search term in the front end. We encapsulate the requirements -in their own ``Demand`` object that basically consists of the properties ``age``, ``organization``, -``region``, ``category`` and ``searchWord``, plus their getters and setters. -In addition to the restrictions for the user's needs, there comes a request -to show the current offers. This example request denotes a date constraint at most one week ago. -In the method ``findDemanded()`` of the ``offerRepository``, the request is implemented: - -.. code-block:: php - :caption: EXT:my_extension/Classes/Domain/Repository/MyRepository.php - - public function findDemanded(\MyVendor\SjrOffers\Domain\Model\Demand $demand) - { - $query = $this->createQuery(); - $constraints = []; - if ($demand->getRegion() !== null) { - $constraints[] = $query->contains('regions', $demand->getRegion()); - } - if ($demand->getCategory() !== null) { - $constraints[] = $query->contains('categories', $demand->getCategory()); - } - if ($demand->getOrganization() !== null) { - $constraints[] = $query->contains('organization', $demand->getOrganization()); - } - if (is_string($demand->getSearchWord()) && strlen($demand->getSearchWord()) > 0) { - $constraints[] = $query->like($propertyName, '%' . $demand->getSearchWord . '%'); - } - if ($demand->getAge() !== null) { - $constraints[] = $query->logicalAnd( - [ - $query->logicalOr( - [ - $query->equals('ageRange.minimumValue', null), - $query->lessThanOrEqual('ageRange.minimumValue', $demand->getAge()) - ] - ), - $query->logicalOr( - [ - $query->equals('ageRange.maximumValue', null), - $query->greaterThanOrEqual('ageRange.maximumValue', $demand->getAge()) - ] - ), - ] - ); - } - $constraints[] = $query->logicalOr( - [ - $query->equals('dateRange.minimumValue', null), - $query->equals('dateRange.minimumValue', 0), - $query->greaterThan('dateRange.maximumValue', (time() - 60*60*24*7)) - ] - ); - - $numberOfConstraints = count($constraints); - if ($numberOfConstraints === 1) { - $query->matching(reset($constraints)); - } elseif ($numberOfConstraints >= 2) { - $query->matching($query->logicalAnd(...$constraints)); - } - return $query->execute(); - } - -The ``Demand`` object is passed as an argument. In the first line, the ``Query`` object is created. -All single constraint terms are then collected in the array ``$constraints``. The -``$query->logicalAnd($constraints)`` instruction brings together these constraint terms, and -they are assigned to the ``Query`` object via ``matching()``. With ``return $query->execute();``, the -query is executed, and the located ``Offer`` objects are returned to the caller. - -The example's offer age range requirement is interesting. - -.. code-block:: php - :caption: EXT:my_extension/Classes/Domain/Repository/MyRepository.php - - $constraints[] = $query->logicalAnd( - [ - $query->logicalOr( - [ - $query->equals('ageRange.minimumValue', null), - $query->lessThanOrEqual('ageRange.minimumValue', $demand->getAge()) - ] - ), - $query->logicalOr( - [ - $query->equals('ageRange.maximumValue', null), - $query->greaterThanOrEqual('ageRange.maximumValue', $demand->getAge()) - ] - ), - ] - ); - -This requirement is fulfilled using multiple levels of nested query constraints. Each ``logicalOr()`` -condition allows either an unset age (value ``equals() null``) or a boundary -age value. (Here, the minimum age is more recent in the past than the maximum age, on a timeline.) -The ``logicalAnd()`` constraint then joins the two ``logicalOr()`` constraints, making a single -constraint, overall. - -.. _extbase_query_orderings: - -Orderings in the query -====================== - -You can sort the result of a query by assigning one or more rules ``$query->setOrderings($orderings);`` -to the ``Query`` object. These rules are collected in an associative array. Each array element has the -property name on which the sort is based as its key, and the search order constant as its value. -There are two constants for the search order: ``\TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING`` -for ascending order, and ``\TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING`` for descending -order. A complete sample for specifying a sort order looks like this: - -.. code-block:: php - :caption: EXT:my_extension/Classes/Domain/Repository/MyRepository.php - - $query->setOrderings( - [ - 'organization.name' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING, - 'title' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING - ] - ); - -Multiple orderings are processed in the specified order. In our sample, the offers are ordered first -by the organization's name, then inside the organization by the title of the offers, both in ascending -order (thus from A to Z). You can use point notation for specifying the property names. - -If you need only an extract of the result set, you can do this with the two parameters, ``Limit`` -and ``Offset``. Assuming you want to get the tenth up to thirtieth offers from the overall query result -from the repository, you can use the following lines: - -.. code-block:: php - :caption: EXT:my_extension/Classes/Domain/Repository/MyRepository.php - - $query->setOffset(10); - $query->setLimit(20); - -Both methods expect an integer value. With the method ``setOffset()``, you set the pointer to the -object you will start with. With the method ``setLimit()``, you set the maximum count of objects you will -get. - -At first sight, using a `Query` object with `Constraint` objects instead -of directly written SQL statements may look inefficient. But doing so here with -Extbase makes complete abstraction of the storage backend possible. - -.. note:: - - The ``Query`` object leans against the *Java Specification Request* (JSR) 283. JSR 283 - describes a standardized content repository for Java; the FLOW team ported this idea to PHP. You can - find more information about this at - https://jcp.org/en/jsr/detail?id=283. - -Even so, using the method ``statement()`` of the ``Query`` object, you can send a native SQL statement to -the database. - -.. code-block:: php - :caption: EXT:my_extension/Classes/Domain/Repository/MyRepository.php - - $result = $query->statement('SELECT * FROM tx_sjroffers_domain_model_offer - WHERE title LIKE ? AND organization IN ?', ['%climbing%', [33,47]]); - -is translated by Extbase to the following query: - -.. code-block:: sql - :caption: Generated SQL example - - SELECT * FROM tx_sjroffers_domain_model_offer WHERE title LIKE '%climbing%' AND - organization IN ('33','47') - -The method ``execute()`` per default returns a ready-built object and the related objects -- the complete *Aggregate*. In some cases, though, it is convenient to preserve the "raw data" of the objects, -e.g., if you want to manipulate them before building objects out of them. For this, you have to execute the -method with its parameter ``$returnRawQueryResult`` set to true. - -.. code-block:: php - :caption: EXT:my_extension/Classes/Domain/Repository/MyRepository.php - - $query->execute(true); - -The method ``execute()`` will then return a multidimensional array with the object data. -Inside an object, one finds single value properties, multi-value properties, and NULL values. Let's have a -look at an object with a single value property. - -.. code-block:: php - :caption: Example object data - - [ - 'identifier' => '', - 'classname' => '', - 'properties' => [ - '' => [ - 'type' => '', - 'multivalue' => FALSE, - 'value' => - ], - ... - ], - ], - -In Extbase, the value for ```` is always the data record's UID. The class name -```` and the identifier together make the element unique across the whole database. The -properties are stored in an own associative array. The property's name is the key, and the -corresponding information of the properties isis the value. The property is signed with the property -type ```` and the property value ```` itself. The property type could be ``string``, ``integer``, -``DateTime``, or a class name like ``\MyVendor\SjrOffers\Domain\Model\Organization``, for example. The property -is declared as a single value per default (``'multivalue' => FALSE``). - -The array of an object with a multi-value property is basically composed the same way. The property's actual value is not a simple data type (like a string or a single object) but an array of data types. -This array could also be empty, and instead of the array, a NULL value is possible. The property type -for multi-value properties is always ``\TYPO3\CMS\Extbase\Persistence\ObjectStorage``. In the future, other containers -like ``array`` or ``splObjectStorage`` may be supported. The property is per definition declared as -multi-value (``'multivalue' => TRUE``). - -.. code-block:: php - :caption: Example object data - - [ - 'identifier' => '', - 'classname' => '', - 'properties' => [ - '' => [ - 'type' => '', // always '\TYPO3\CMS\Extbase\Persistence\ObjectStorage' - 'multivalue' => TRUE, - 'value' => [ - [ - 'type' => '', - 'index' => , - 'value' => - ], - ... - ], - ], - ], - ], - -If a property has a NULL value, it is stored in the object array like this: - -.. code-block:: php - :caption: Example object data - - [ - 'identifier' => '', - 'classname' => '', - 'properties' => [ - '' => [ - 'type' => '', - 'multivalue' => , - 'value' => NULL - ], - ... - ], - ], - -The debug output of the return value looks like figure 6-13. - -.. figure:: /Images/ManualScreenshots/Frontend/6-Persistence/figure-6-13.png - :align: center - - Figure 6-13: Debug output of "raw" object data - -Maybe in figure 6-13, you have noticed the empty array (``EMPTY!``) of the organization's properties. -In the domain model, the property ``organization`` of the offer is annotated with :php:`@TYPO3\CMS\Extbase\Annotation\ORM\Lazy`. -This annotation instructs Extbase to load the object's properties only when they are really -needed (so-called *lazy loading*). - -There are three additional settings for the execution of a query. -All settings are occupied with default values that are set when the ``Query`` object was created by -``$this->createQuery()``. The settings are enclosed in an own ``QuerySettings`` object that you can get -from the ``Query`` object with ``getQuerySettings()``. In table 6-3, you find all settings in summary. - -*Table 6-3: Settings for the execution of a query* (``QuerySetting``) - -+-------------------------------+-------------------------------------------------------------+---------+ -| Setting | If this attribute is set (=true), ... | Default | -+===============================+=============================================================+=========+ -| ``setRespectStoragePage()`` | ... the result set is limited to these tuples/objects that | true | -| | are assigned to a given page or directory in the backend | | -| | (e.g. ``pid IN (42,99)``) | | -+-------------------------------+-------------------------------------------------------------+---------+ -| ``setRespectSysLanguage()`` | ... the result set for localized data is limited to these | true | -| | tuples/objects valid for either the default language | | -| | or for all languages (e.g. ``sys_language_uid IN (-1,0)``) | | -| | This setting is mostly used for internal purposes. | | -+-------------------------------+-------------------------------------------------------------+---------+ -| ``setIgnoreEnableFields()`` | ... the result set is limited to these tuples/objects that | false | -| | at the present moment can be viewed by the current user | | -| | (e.g. ``deleted=0 AND hidden=0``) | | -+-------------------------------+-------------------------------------------------------------+---------+ - - -Beside the method ``execute()``, the ``Query`` object provides the method ``count()`` for disposal. -It returns only the number of elements of the result set, as an integer value, and can only be used in -conjunction with the method ``matching()``. In a backend SQL database, a statement of the form -``SELECT COUNT(*) FROM ...`` would be sent, which has significantly more performance than ``SELECT * FROM ...``. - -In any backend storage case, the call - -.. code-block:: php - :caption: EXT:my_extension/Classes/Domain/Repository/MyRepository.php - - $offersInRegion = $query->matching($query->contains('regions', $region))->count(); - -thus returns the count of offers of a given region. - - -.. index:: Cardinality handling - -Implicit relation cardinality handling -====================================== - -Extbase supports several types of cardinalities that describe the relationship -between entities - among these are RELATION_HAS_ONE (1:1), -RELATION_HAS_MANY (1:n) and RELATION_HAS_AND_BELONGS_TO_MANY (m:n). - -Using these types in individual queries will result in invoking an implicit -`LEFT JOIN` on the database layer. The following sections are using the -EXT:blog_example to explain what happens under the hood in terms of database -queries. The used entities are the following: - -* `Blog.posts` having 1:n relation to `Post` -* `Post.author` having 1:1 relation to `Person` -* `Person.tags` having m:n relation to `Tag` -* `Person.tagsSpecial` having m:n relation to `Tag` - -.. note:: - - The table names in the following SQL-like examples have been shortened for - better readability. Instead of `tx_blogexample_post` the real table name - used in the **Blog Example** would be `tx_blogexample_domain_model_post`. - Besides that, only the relevant query parts, as mentioned, not all of them. - - -.. index:: Cardinality handling; 1:1 - -1:1 (RELATION_HAS_ONE) ----------------------- - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OrganizationRepository.php - - $query = $postRepository->createQuery(); - $query->matching( - $query->equals('author.firstname', 'Dave') - ); - $posts = $query->execute(); - -.. code-block:: sql - :caption: Generated SQL example - - SELECT tx_blogexample_post.* - FROM tx_blogexample_post - LEFT JOIN tx_blogexample_person - ON tx_blogexample_post.author = tx_blogexample_person.uid - WHERE tx_blogexample_person.firstname = 'Dave'; - -Even if the SQL-like query contains a ``LEFT JOIN``, due to the 1:1 cardinality -this won't lead to duplicate results for ``Post`` entities. - - -.. index:: Cardinality handling; 1:n - -1:n (RELATION_HAS_MANY) ------------------------ - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OrganizationRepository.php - - $query = $blogRepository->createQuery(); - $query->matching( - $query->greaterThanOrEqual('posts.date', 1501234567) - ); - $blogs = $query->execute(); - -.. code-block:: sql - :caption: Generated SQL example - - SELECT DISTINCT tx_blogexample_blog.* - FROM tx_blogexample_blog - LEFT JOIN tx_blogexample_post - ON tx_blogexample_blog.uid = tx_blogexample_post.blog - WHERE tx_blogexample_post.date >= 1501234567; - -Since there might be more ``Post`` entities belonging to a single ``Blog`` -entity it could happen that the ``LEFT JOIN`` results in having many duplicate -``Blog`` entities in the result set. - -.. index:: Cardinality handling; m:n - -m:n (RELATION_HAS_AND_BELONGS_TO_MANY) --------------------------------------- - -.. code-block:: php - :caption: EXT:sjr_offers/Classes/Domain/Repository/OrganizationRepository.php - - $query = $postRepository->createQuery(); - $query->matching( - $query->logicalOr([ - $query->equals('author.tags.name', 'typo3'), - $query->equals('author.tagsSpecial.name', 'typo3') - ]) - ); - $posts = $query->execute(); - -.. code-block:: sql - :caption: Generated SQL example - - SELECT DISTINCT tx_blogexample_post.* - FROM tx_blogexample_post - LEFT JOIN tx_blogexample_person - ON tx_blogexample_post.author = tx_blogexample_person.uid - LEFT JOIN tx_blogexample_tag_mm tx_blogexample_tag_mm_1 - ON tx_blogexample_tag_mm_1.uid_local = tx_blogexample_person.uid - AND tx_blogexample_tag_mm_1.fieldname = 'tags' - LEFT JOIN tx_blogexample_tag_mm tx_blogexample_tag_mm_2 - ON tx_blogexample_tag_mm_2.uid_local = tx_blogexample_person.uid - AND tx_blogexample_tag_mm_2.fieldname = 'tags_special' - LEFT JOIN tx_blogexample_tag tx_blogexample_tag_1 - ON tx_blogexample_tag_mm_1.uid_foreign = tx_blogexample_tag_1.uid - LEFT JOIN tx_blogexample_tag tx_blogexample_tag_2 - ON tx_blogexample_tag_mm_2.uid_foreign = tx_blogexample_tag_2.uid - WHERE tx_blogexample_tag_1.name = 'typo3' - OR tx_blogexample_tag_2.name = 'typo3'; - -Since the nature of a many-to-many relation is to be used by various entities, -this will also lead to lots of duplicated `Post` entities in the result set in -this rather complex query example. - -Distinct entity handling in query result set --------------------------------------------- - -+-----------------------------------------+--------------------------------------------------+ -| Cardinality | distinct entity handling suggested | -+=========================================+=====+============================================+ -| 1:1 (RELATION_HAS_ONE) | no | since for each left-sided entity there | -| | | is always just one right-sided entity | -+-----------------------------------------+-----+--------------------------------------------+ -| 1:n (RELATION_HAS_MANY) | yes | since having more than one right-sided | -| | | entity will lead to left-sided duplicates | -+-----------------------------------------+-----+--------------------------------------------+ -| m:n (RELATION_HAS_AND_BELONGS_TO_MANY) | yes | since having more than one right-sided | -| | | entity will lead to left-sided duplicates | -+-----------------------------------------+-----+--------------------------------------------+ - -For each of the above-mentioned scenarios, when having distinct entity handling -is suggested, an implicit `SELECT DISTINCT` statement is used instead of the -regular plain `SELECT` statement. This does also apply to counting result -sets where `COUNT(DISTINCT .uid)` is used instead of a plain -`COUNT(*)` statement. diff --git a/Documentation/6-Persistence/4-use-foreign-data-sources.rst b/Documentation/6-Persistence/4-use-foreign-data-sources.rst deleted file mode 100644 index 1cfd44dd..00000000 --- a/Documentation/6-Persistence/4-use-foreign-data-sources.rst +++ /dev/null @@ -1,57 +0,0 @@ -.. include:: /Includes.rst.txt -.. _using-foreign-data-sources: - -========================== -Using foreign data sources -========================== - -In real projects, many times, data from different sources have to be ordered. One target of Extbase is -to normalize the access to these data sources and to abstract it from the concrete technical solution. -These "foreign" data sources could be tables from the same TYPO3 database or a web service. - -Extbase building-up strongly of the rule "Convention over Configuration" (see also the appendix A for this). -Foreign database tables rarely correspond with the conventions of Extbase. Therefore, the class assignment -to a given table and the assignment of field names to property names of the classes -must be configured via PHP. This assignment is also called *mapping*. The following configuration -enables the storage of the object data of a class :php:`\MyVendor\MyExtension\Domain\Model\Person` in the table -`tt_address`, which is available in most TYPO3 installations. - - -.. code-block:: php - :caption: EXT:my_extension/Configuration/Extbase/Persistence/Classes.php - - [ - 'tableName' => 'tt_address', - 'recordType' => \MyVendor\MyExtension\Domain\Model\Person::class, - 'properties' => [ - 'dateOfBirth' => [ - 'fieldName' => 'birthday', - ], - 'thoroughfare' => [ - 'fieldName' => 'street', - ], - ], - ], - ]; - -The configuration file must return an array with a configuration for each class that needs to be mapped to a database -table. The configuration is quite straight forward. The :php:`tableName` obviously defines the table the class is mapped -to. When using single table inheritance, the type of record can be set via :php:`recordType`. The :php:`properties` key -defines a set of class properties that should be mapped onto fields that don't comply with the naming convention. - -.. note:: - - Regard in each case that the field type fits the data type of your property. You will find - additional information in "Preparing the tables of the domain objects" above in this chapter. - -This configuration causes Extbase to use the table ``tt_address`` when reconstructing or persisting of -objects of the class :php:`\MyVendor\MyExtension\Domain\Model\Person`. Thereby the values of the properties -``dateOfBirth`` and ``thoroughfare`` are stored in the fields ``birthday`` and ``street``. If the -configuration option ``tableName`` is not set, Extbase searches for a table that corresponds to the lower -case class name, in our case: ``tx_myextension_domain_model_person``. If -there is no mapping defined for a property, the property name, translated in lower case with underscores, -is expected as the field name. The property name `dateOfBirth` would result in a field name `date_of_birth`. diff --git a/Documentation/6-Persistence/Index.rst b/Documentation/6-Persistence/Index.rst index 35696e58..9f4c0928 100644 --- a/Documentation/6-Persistence/Index.rst +++ b/Documentation/6-Persistence/Index.rst @@ -48,9 +48,4 @@ Let's start with the database. .. toctree:: :hidden: - 1-prepare-the-database - 2-configure-the-backends-inputforms - 2a-creating-the-repositories - 3-implement-individual-database-queries - 4-use-foreign-data-sources 5-modeling-the-class-hierarchy