TASK: Add update record, without validation

This commit is contained in:
Daniel Siepmann 2018-10-01 13:51:18 +02:00
parent f7f4be0335
commit 89b63c3f43
Signed by: Daniel Siepmann
GPG key ID: 33D6629915560EF4
12 changed files with 370 additions and 44 deletions

View file

@ -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');
}
}

View file

@ -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;

View file

@ -6,4 +6,9 @@
'pluginkey', 'pluginkey',
'Example Plugin' 'Example Plugin'
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerPlugin(
'Workshop.ExampleExtension',
'Address',
'Address Plugin'
);
})(); })();

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -8,4 +8,15 @@
'Example' => 'example' 'Example' => 'example'
] ]
); );
\TYPO3\CMS\Extbase\Utility\ExtensionUtility::configurePlugin(
'Workshop.ExampleExtension',
'Address',
[
'Address' => 'index, edit, update'
],
[
'Address' => 'update'
]
);
})(); })();

View file

@ -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
-------------------- --------------------

View file

@ -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
--------------------- ---------------------

View file

@ -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.

View file

@ -0,0 +1,3 @@
.admonition-task {
border-left-color: #ad7526;
}

View file

@ -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')