TASK: Add update record, without validation
This commit is contained in:
parent
f7f4be0335
commit
89b63c3f43
12 changed files with 370 additions and 44 deletions
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Workshop\ExampleExtension\Controller;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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\Mvc\Controller\ActionController;
|
||||||
|
use Workshop\ExampleExtension\Domain\Model\Address;
|
||||||
|
use Workshop\ExampleExtension\Domain\Repository\AddressRepository;
|
||||||
|
|
||||||
|
class AddressController extends ActionController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var AddressRepository
|
||||||
|
*/
|
||||||
|
protected $addressRepository;
|
||||||
|
|
||||||
|
public function __construct(AddressRepository $addressRepository)
|
||||||
|
{
|
||||||
|
$this->addressRepository = $addressRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function indexAction()
|
||||||
|
{
|
||||||
|
$this->view->assign('addresses', $this->addressRepository->findAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function editAction(Address $address)
|
||||||
|
{
|
||||||
|
$this->view->assign('address', $address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateAction(Address $address)
|
||||||
|
{
|
||||||
|
$this->addressRepository->update($address);
|
||||||
|
|
||||||
|
$this->addFlashMessage(
|
||||||
|
$address->getCompanyName() . ' was updated.',
|
||||||
|
'Update successfully'
|
||||||
|
);
|
||||||
|
$this->redirect('index');
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,31 +55,61 @@ class Address extends AbstractEntity
|
||||||
*/
|
*/
|
||||||
protected $country;
|
protected $country;
|
||||||
|
|
||||||
|
public function setCompanyName(string $companyName)
|
||||||
|
{
|
||||||
|
$this->companyName = $companyName;
|
||||||
|
}
|
||||||
|
|
||||||
public function getCompanyName(): string
|
public function getCompanyName(): string
|
||||||
{
|
{
|
||||||
return $this->companyName;
|
return $this->companyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setStreet(string $street)
|
||||||
|
{
|
||||||
|
$this->street = $street;
|
||||||
|
}
|
||||||
|
|
||||||
public function getStreet(): string
|
public function getStreet(): string
|
||||||
{
|
{
|
||||||
return $this->street;
|
return $this->street;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setHouseNumber(string $houseNumber)
|
||||||
|
{
|
||||||
|
$this->houseNumber = $houseNumber;
|
||||||
|
}
|
||||||
|
|
||||||
public function getHouseNumber(): string
|
public function getHouseNumber(): string
|
||||||
{
|
{
|
||||||
return $this->houseNumber;
|
return $this->houseNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setZip(string $zip)
|
||||||
|
{
|
||||||
|
$this->zip = $zip;
|
||||||
|
}
|
||||||
|
|
||||||
public function getZip(): string
|
public function getZip(): string
|
||||||
{
|
{
|
||||||
return $this->zip;
|
return $this->zip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setCity(string $city)
|
||||||
|
{
|
||||||
|
$this->city = $city;
|
||||||
|
}
|
||||||
|
|
||||||
public function getCity(): string
|
public function getCity(): string
|
||||||
{
|
{
|
||||||
return $this->city;
|
return $this->city;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setCountry(string $country)
|
||||||
|
{
|
||||||
|
$this->country = $country;
|
||||||
|
}
|
||||||
|
|
||||||
public function getCountry(): string
|
public function getCountry(): string
|
||||||
{
|
{
|
||||||
return $this->country;
|
return $this->country;
|
||||||
|
|
|
@ -6,4 +6,9 @@
|
||||||
'pluginkey',
|
'pluginkey',
|
||||||
'Example Plugin'
|
'Example Plugin'
|
||||||
);
|
);
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'Address',
|
||||||
|
'Address Plugin'
|
||||||
|
);
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?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-10-01T11:16:33Z" product-name="CodeExamples">
|
||||||
|
<header/>
|
||||||
|
<body>
|
||||||
|
<!-- Form Labels -->
|
||||||
|
<trans-unit id="labels.companyName" xml:space="preserve">
|
||||||
|
<source>Company name</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.street" xml:space="preserve">
|
||||||
|
<source>Street</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.houseNumber" xml:space="preserve">
|
||||||
|
<source>House number</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.zip" xml:space="preserve">
|
||||||
|
<source>Zip</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.city" xml:space="preserve">
|
||||||
|
<source>City</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="labels.country" xml:space="preserve">
|
||||||
|
<source>Country</source>
|
||||||
|
</trans-unit>
|
||||||
|
</body>
|
||||||
|
</file>
|
||||||
|
</xliff>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<h3>Editing: {address.companyName}</h3>
|
||||||
|
|
||||||
|
<f:form action="update" object="{address}" name="address">
|
||||||
|
<f:for each="{
|
||||||
|
0: 'companyName',
|
||||||
|
1: 'street',
|
||||||
|
2: 'houseNumber',
|
||||||
|
3: 'zip',
|
||||||
|
4: 'city',
|
||||||
|
5: 'country'
|
||||||
|
}" as="propertyName">
|
||||||
|
<p>
|
||||||
|
<label for="{propertyName}">
|
||||||
|
{f:translate(id: 'labels.{propertyName}')}
|
||||||
|
</label>
|
||||||
|
<br>
|
||||||
|
<f:form.textfield property="{propertyName}" id="{propertyName}" />
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
</f:for>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<f:form.submit value="Update" />
|
||||||
|
</f:form>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<f:for each="{addresses}" as="address">
|
||||||
|
<h3>{address.companyName}</h3>
|
||||||
|
<address>
|
||||||
|
{address.street} {address.houseNumber}
|
||||||
|
{address.zip} {address.city}
|
||||||
|
{address.country}
|
||||||
|
</address>
|
||||||
|
|
||||||
|
<f:link.action action="edit" arguments="{address: address}">Edit</f:link.action>
|
||||||
|
</f:for>
|
|
@ -8,4 +8,15 @@
|
||||||
'Example' => 'example'
|
'Example' => 'example'
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'Address',
|
||||||
|
[
|
||||||
|
'Address' => 'index, edit, update'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Address' => 'update'
|
||||||
|
]
|
||||||
|
);
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -31,8 +31,15 @@ available as a new option for the content element *Insert Plugin*.
|
||||||
This is done with the following code in file
|
This is done with the following code in file
|
||||||
:file:`Configuration/TCA/Overrides/tt_content.php`:
|
:file:`Configuration/TCA/Overrides/tt_content.php`:
|
||||||
|
|
||||||
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Configuration/TCA/Overrides/tt_content.php
|
.. code-block:: php
|
||||||
:language: php
|
|
||||||
|
(function () {
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'pluginkey',
|
||||||
|
'Example Plugin'
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
Configure Plugin for Frontend
|
Configure Plugin for Frontend
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
@ -44,8 +51,18 @@ Configure Plugin for Frontend
|
||||||
To actually call some PHP Code when the content element is rendered, we need to
|
To actually call some PHP Code when the content element is rendered, we need to
|
||||||
configure the plugin in :file:`ext_localconf.php`:
|
configure the plugin in :file:`ext_localconf.php`:
|
||||||
|
|
||||||
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/ext_localconf.php
|
.. code-block:: php
|
||||||
:language: php
|
|
||||||
|
(function () {
|
||||||
|
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
|
||||||
|
'Workshop.ExampleExtension',
|
||||||
|
'pluginkey',
|
||||||
|
[
|
||||||
|
'Example' => 'example'
|
||||||
|
]
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
||||||
Write necessary Code
|
Write necessary Code
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
@ -90,7 +90,7 @@ Each model is a PHP class structure like:
|
||||||
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Domain/Model/Address.php
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Domain/Model/Address.php
|
||||||
:language: php
|
:language: php
|
||||||
:linenos:
|
:linenos:
|
||||||
:lines: 1-4,24-32,58-61,87
|
:lines: 1-4,24-32,63-66,87
|
||||||
|
|
||||||
Repository
|
Repository
|
||||||
----------
|
----------
|
||||||
|
@ -121,24 +121,10 @@ Controller
|
||||||
In order to provide records in form of models to our template, we first need an
|
In order to provide records in form of models to our template, we first need an
|
||||||
instance of our repository:
|
instance of our repository:
|
||||||
|
|
||||||
.. code-block:: php
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Controller/AddressController.php
|
||||||
|
:language: php
|
||||||
:linenos:
|
:linenos:
|
||||||
|
:lines: 1-4, 24-38,66
|
||||||
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
|
With the above code we only can create instances of the controller if an instance of
|
||||||
the Repository is provided.
|
the Repository is provided.
|
||||||
|
@ -151,16 +137,10 @@ 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
|
We then can call the accordingly method to fetch records, which then can be assigned
|
||||||
to the view:
|
to the view:
|
||||||
|
|
||||||
.. code-block:: php
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Classes/Controller/AddressController.php
|
||||||
|
:language: php
|
||||||
:linenos:
|
:linenos:
|
||||||
|
:lines: 28-29,40-43,66
|
||||||
class ExampleController extends ActionController
|
|
||||||
{
|
|
||||||
public function exampleAction()
|
|
||||||
{
|
|
||||||
$this->view->assign('addresses', $this->addressRepository->findAll());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
The ``AddressRepository`` extends the base ``Repository`` class and inherits some
|
The ``AddressRepository`` extends the base ``Repository`` class and inherits some
|
||||||
methods, e.g. ``findAll``.
|
methods, e.g. ``findAll``.
|
||||||
|
@ -170,19 +150,12 @@ Template
|
||||||
|
|
||||||
With our records in our template, we can iterate over them to display them.
|
With our records in our template, we can iterate over them to display them.
|
||||||
|
|
||||||
:file:`Resources/Private/Templates/Example/Example.html`:
|
:file:`Resources/Private/Templates/Address/Index.html`:
|
||||||
|
|
||||||
.. code-block:: html
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Resources/Private/Templates/Address/Index.html
|
||||||
|
:language: html
|
||||||
:linenos:
|
:linenos:
|
||||||
|
:lines: 1-7,10
|
||||||
<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
|
Configure storage pid
|
||||||
---------------------
|
---------------------
|
||||||
|
|
|
@ -1,12 +1,174 @@
|
||||||
|
.. highlight:: php
|
||||||
|
|
||||||
Updating records
|
Updating records
|
||||||
================
|
================
|
||||||
|
|
||||||
Form
|
For everything we have done so far, you do not need a plugin at all. Custom records
|
||||||
----
|
only need :file:`ext_tables.sql` the TCA and TypoScript for rendering.
|
||||||
|
|
||||||
|
Extbase is needed if you provide interaction to the user, e.g. updating or adding
|
||||||
|
records.
|
||||||
|
|
||||||
|
Even that can nowadays achieved using the system extension "Form". Still we will
|
||||||
|
cover how to update a record next.
|
||||||
|
|
||||||
|
We need a form where we can adjust the values of the record, e.g. change the
|
||||||
|
companies name.
|
||||||
|
|
||||||
|
Therefore we will add a new ``editAction()``. This will receive a single ``Address``
|
||||||
|
for editing and provides the form. We also will add an ``updateAction()`` which
|
||||||
|
receives a single ``Address``. This action is the target of the submit form and will
|
||||||
|
update the database record.
|
||||||
|
|
||||||
|
To start editing an address, we will add an link from :file:`index.html` to
|
||||||
|
``editAction()``.
|
||||||
|
|
||||||
|
Link to another action
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create a link to the ``editAction()`` providing the "current" Address record.
|
||||||
|
|
||||||
|
TYPO3 provides ViewHelpers to create links to different actions. To insert a Link to
|
||||||
|
edit a record, you could use the following:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../CodeExamples/localPackages/example_extension/Resources/Private/Templates/Address/Index.html
|
||||||
|
:language: html
|
||||||
|
:linenos:
|
||||||
|
:dedent: 4
|
||||||
|
:lines: 9
|
||||||
|
|
||||||
|
The ViewHelper generates a link, thanks to the current plugin context, all arguments
|
||||||
|
are prefixed with the plugin namespace.
|
||||||
|
|
||||||
|
Creating Forms
|
||||||
|
--------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Create a form which will show the current values of a single ``Address`` to the
|
||||||
|
user.
|
||||||
|
|
||||||
|
TYPO3 also provides ViewHelpers to create forms. Of course you could create form with
|
||||||
|
pure HTML, but TYPO3 / Fluid adds some security aspects and makes things easier.
|
||||||
|
|
||||||
|
E.g. a proper validation that forms were not manipulated are added out of the box.
|
||||||
|
Also you do not need to take care of proper names of the inputs to allow Extbase to
|
||||||
|
map incoming inputs.
|
||||||
|
|
||||||
|
A basic form looks like:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
<f:form action="update" object="{address}" name="address">
|
||||||
|
<f:form.textfield property="companyName" />
|
||||||
|
<f:form.submit value="Update" />
|
||||||
|
</f:form>
|
||||||
|
|
||||||
Persistence
|
Persistence
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Save changes to database.
|
||||||
|
|
||||||
|
Once the user submits the form, the ``updateAction()`` method within our controller is
|
||||||
|
called. We therefore have to implement this method and to persist the changes that
|
||||||
|
were submitted.
|
||||||
|
|
||||||
|
We already have an instance of the accordingly repository within our controller. We
|
||||||
|
also receive the modified object as an argument within our action. All we have to do
|
||||||
|
is to update the record within the repository::
|
||||||
|
|
||||||
|
public function updateAction(Address $address)
|
||||||
|
{
|
||||||
|
$this->addressRepository->update($address);
|
||||||
|
}
|
||||||
|
|
||||||
|
At the end of the "request", Extbase will cleanup everything and persist the updates
|
||||||
|
to the backend, which in our case is MySQL.
|
||||||
|
|
||||||
|
Redirect
|
||||||
|
--------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Redirect back to index.
|
||||||
|
|
||||||
|
All our changes are already saved to database, but the user receives an error that no
|
||||||
|
template could be found. We actually do not need any template for our
|
||||||
|
``updateAction()``. Instead we will redirect the user back to the ``indexAction()``.
|
||||||
|
This way he can check whether the change has effected the output and works as
|
||||||
|
expected.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Following REST, an update returns the updated resource, which is the uri to the
|
||||||
|
resource, in our case the redirect.
|
||||||
|
|
||||||
|
As Browsers do not support ``PATCH``, which would be the request method, we use
|
||||||
|
``POST``, see: https://en.wikipedia.org/wiki/Representational_state_transfer#Relationship_between_URL_and_HTTP_methods
|
||||||
|
|
||||||
|
As we extend ``ActionController`` we can use the following line to redirect to
|
||||||
|
another action::
|
||||||
|
|
||||||
|
$this->redirect('index');
|
||||||
|
|
||||||
|
Cache clearing
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Even if the user is redirected, he does not see any difference. That's due to TYPO3
|
||||||
|
caching.
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Make changes visible in index action.
|
||||||
|
|
||||||
|
Extbase, by default, clears the cache for all updated records. Therefore the page
|
||||||
|
cache for the pages holding the records is cleared. As our plugin resists on a
|
||||||
|
different page, we have to configure TYPO3.
|
||||||
|
|
||||||
|
Same is true for plain TYPO3 Backend. As soon as a record is edited, the page cache
|
||||||
|
is cleared. If this record is displayed on another page, the caching has to be
|
||||||
|
configured, so this is not Extbase specific.
|
||||||
|
|
||||||
|
The caching can be configured using Page TS Config:
|
||||||
|
|
||||||
|
.. code-block:: typoscript
|
||||||
|
|
||||||
|
TCEMAIN {
|
||||||
|
clearCacheCmd = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
See: https://docs.typo3.org/typo3cms/TSconfigReference/PageTsconfig/TceMain.html#clearcachecmd
|
||||||
|
|
||||||
|
Flash message
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Inform user about what happened.
|
||||||
|
|
||||||
|
We now have a fully working process. Still in a long list of records, the user might
|
||||||
|
not notice a difference. Also if he leaves the computer and comes back, he will not
|
||||||
|
know what was done before.
|
||||||
|
|
||||||
|
Extbase has a feature called "Flashmessages" which are also used within TYPO3
|
||||||
|
Backend. They inform a user on next page about some thing that happened during
|
||||||
|
the last request. We could use that to add a message about which record was updated.
|
||||||
|
This is also just one line within a controller::
|
||||||
|
|
||||||
|
$this->addFlashMessage(
|
||||||
|
$address->getCompanyName() . ' was updated.',
|
||||||
|
'Update successfully'
|
||||||
|
);
|
||||||
|
|
||||||
Validation
|
Validation
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
.. admonition:: Task
|
||||||
|
|
||||||
|
Prevent invalid data.
|
||||||
|
|
||||||
|
|
3
Documentation/source/_static/css/custom.css
Normal file
3
Documentation/source/_static/css/custom.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.admonition-task {
|
||||||
|
border-left-color: #ad7526;
|
||||||
|
}
|
|
@ -317,3 +317,7 @@ intersphinx_mapping = {
|
||||||
from sphinx.highlighting import lexers
|
from sphinx.highlighting import lexers
|
||||||
from pygments.lexers.web import PhpLexer
|
from pygments.lexers.web import PhpLexer
|
||||||
lexers['php'] = PhpLexer(startinline=True, linenos=1)
|
lexers['php'] = PhpLexer(startinline=True, linenos=1)
|
||||||
|
|
||||||
|
# Add custom css
|
||||||
|
def setup(app):
|
||||||
|
app.add_stylesheet('css/custom.css')
|
||||||
|
|
Loading…
Reference in a new issue