Skip to content

Commit feb8606

Browse files
committed
Merge branch '3.4' into 4.0
* 3.4: (21 commits) added Sam in the core team minor #9494 [Console] Change use statement for Process Helper (krizon) Specify how provide a high availability Use FQCN as service ID [#8847] fix another directive [#8847] fix tip directive Describe reliability in Lock Propose identical comparison [#9195] fix a minor typo Fixed some variable names fix some security config examples Made explicit testing dependencies No more ambiguity on prod mode Mention the adder/remover support of PropertyInfo ...
2 parents e523f0f + d5542c9 commit feb8606

File tree

11 files changed

+383
-86
lines changed

11 files changed

+383
-86
lines changed

best_practices/security.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ to the ``Post`` entity that checks if a given user is its author::
181181
*/
182182
public function isAuthor(User $user = null)
183183
{
184-
return $user && $user->getEmail() == $this->getAuthorEmail();
184+
return $user && $user->getEmail() === $this->getAuthorEmail();
185185
}
186186
}
187187

components/console/helpers/processhelper.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ To display process details, use the :class:`Symfony\\Component\\Console\\Helper\
1111
and run your command with verbosity. For example, running the following code with
1212
a very verbose verbosity (e.g. -vv)::
1313

14-
use Symfony\Component\Process\ProcessBuilder;
14+
use Symfony\Component\Process\Process;
1515

1616
$helper = $this->getHelper('process');
1717
$process = new Process(array('figlet', 'Symfony'));
@@ -52,7 +52,7 @@ There are three ways to use the process helper:
5252

5353
* Passing a :class:`Symfony\\Component\\Process\\Process` instance::
5454

55-
use Symfony\Component\Process\ProcessBuilder;
55+
use Symfony\Component\Process\Process;
5656

5757
// ...
5858
$process = new Process(array('figlet', 'Symfony'));

components/lock.rst

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,233 @@ Instead of the simple majority strategy (``ConsensusStrategy``) an
257257
``UnanimousStrategy`` can be used to require the lock to be acquired in all
258258
the stores.
259259

260+
.. caution::
261+
262+
In order to get high availability when using the ``ConsensusStrategy``, the
263+
minimum cluster size must be three servers. This allows the cluster to keep
264+
working when a single server fails (because this strategy requires that the
265+
lock is acquired in more than half of the servers).
266+
267+
Reliability
268+
-----------
269+
270+
The component guarantees that the same resource can't be lock twice as long as
271+
the component is used in the following way.
272+
273+
Remote Stores
274+
~~~~~~~~~~~~~
275+
276+
Remote stores (:ref:`MemcachedStore <lock-store-memcached>` and
277+
:ref:`RedisStore <lock-store-redis>`) use an unique token to recognize the true
278+
owner of the lock. This token is stored in the
279+
:class:`Symfony\\Component\\Lock\\Key` object and is used internally by the
280+
``Lock``, therefore this key must not be shared between processes (session,
281+
caching, fork, ...).
282+
283+
.. caution::
284+
285+
Do not share a key between processes.
286+
287+
Every concurrent process must store the ``Lock`` in the same server. Otherwise two
288+
different machines may allow two different processes to acquire the same ``Lock``.
289+
290+
.. caution::
291+
292+
To guarantee that the same server will always be safe, do not use Memcached
293+
behind a LoadBalancer, a cluster or round-robin DNS. Even if the main server
294+
is down, the calls must not be forwarded to a backup or failover server.
295+
296+
Expiring Stores
297+
~~~~~~~~~~~~~~~
298+
299+
Expiring stores (:ref:`MemcachedStore <lock-store-memcached>` and
300+
:ref:`RedisStore <lock-store-redis>`) guarantee that the lock is acquired
301+
only for the defined duration of time. If the task takes longer to be
302+
accomplished, then the lock can be released by the store and acquired by
303+
someone else.
304+
305+
The ``Lock`` provides several methods to check its health. The ``isExpired()``
306+
method checks whether or not it lifetime is over and the ``getRemainingLifetime()``
307+
method returns its time to live in seconds.
308+
309+
Using the above methods, a more robust code would be::
310+
311+
// ...
312+
$lock = $factory->createLock('invoice-publication', 30);
313+
314+
$lock->acquire();
315+
while (!$finished) {
316+
if ($lock->getRemainingLifetime() <= 5) {
317+
if ($lock->isExpired()) {
318+
// lock was lost, perform a rollback or send a notification
319+
throw new \RuntimeException('Lock lost during the overall process');
320+
}
321+
322+
$lock->refresh();
323+
}
324+
325+
// Perform the task whose duration MUST be less than 5 minutes
326+
}
327+
328+
.. caution::
329+
330+
Choose wisely the lifetime of the ``Lock`` and check whether its remaining
331+
time to leave is enough to perform the task.
332+
333+
.. caution::
334+
335+
Storing a ``Lock`` usually takes a few milliseconds, but network conditions
336+
may increase that time a lot (up to a few seconds). Take that into account
337+
when choosing the right TTL.
338+
339+
By design, locks are stored in servers with a defined lifetime. If the date or
340+
time of the machine changes, a lock could be released sooner than expected.
341+
342+
.. caution::
343+
344+
To guarantee that date won't change, the NTP service should be disabled
345+
and the date should be updated when the service is stopped.
346+
347+
FlockStore
348+
~~~~~~~~~~
349+
350+
By using the file system, this ``Store`` is reliable as long as concurrent
351+
processes use the same physical directory to stores locks.
352+
353+
Processes must run on the same machine, virtual machine or container.
354+
Be careful when updating a Kubernetes or Swarm service because for a short
355+
period of time, there can be two running containers in parallel.
356+
357+
The absolute path to the directory must remain the same. Be careful of symlinks
358+
that could change at anytime: Capistrano and blue/green deployment often use
359+
that trick. Be careful when the path to that directory changes between two
360+
deployments.
361+
362+
Some file systems (such as some types of NFS) do not support locking.
363+
364+
.. caution::
365+
366+
All concurrent processes must use the same physical file system by running
367+
on the same machine and using the same absolute path to locks directory.
368+
369+
By definition, usage of ``FlockStore`` in an HTTP context is incompatible
370+
with multiple front servers, unless to ensure that the same resource will
371+
always be locked on the same machine or to use a well configured shared file
372+
system.
373+
374+
Files on file system can be removed during a maintenance operation. For instance
375+
to cleanup the ``/tmp`` directory or after a reboot of the machine when directory
376+
uses tmpfs. It's not an issue if the lock is released when the process ended, but
377+
it is in case of ``Lock`` reused between requests.
378+
379+
.. caution::
380+
381+
Do not store locks on a volatile file system if they have to be reused in
382+
several requests.
383+
384+
MemcachedStore
385+
~~~~~~~~~~~~~~
386+
387+
The way Memcached works is to store items in memory. That means that by using
388+
the :ref:`MemcachedStore <lock-store-memcached>` the locks are not persisted
389+
and may disappear by mistake at anytime.
390+
391+
If the Memcached service or the machine hosting it restarts, every lock would
392+
be lost without notifying the running processes.
393+
394+
.. caution::
395+
396+
To avoid that someone else acquires a lock after a restart, it's recommended
397+
to delay service start and wait at least as long as the longest lock TTL.
398+
399+
By default Memcached uses a LRU mechanism to remove old entries when the service
400+
needs space to add new items.
401+
402+
.. caution::
403+
404+
Number of items stored in the Memcached must be under control. If it's not
405+
possible, LRU should be disabled and Lock should be stored in a dedicated
406+
Memcached service away from Cache.
407+
408+
When the Memcached service is shared and used for multiple usage, Locks could be
409+
removed by mistake. For instance some implementation of the PSR-6 ``clear()``
410+
method uses the Memcached's ``flush()`` method which purges and removes everything.
411+
412+
.. caution::
413+
414+
The method ``flush()`` must not be called, or locks should be stored in a
415+
dedicated Memcached service away from Cache.
416+
417+
RedisStore
418+
~~~~~~~~~~
419+
420+
The way Redis works is to store items in memory. That means that by using
421+
the :ref:`RedisStore <lock-store-redis>` the locks are not persisted
422+
and may disappear by mistake at anytime.
423+
424+
If the Redis service or the machine hosting it restarts, every locks would
425+
be lost without notifying the running processes.
426+
427+
.. caution::
428+
429+
To avoid that someone else acquires a lock after a restart, it's recommended
430+
to delay service start and wait at least as long as the longest lock TTL.
431+
432+
.. tip::
433+
434+
Redis can be configured to persist items on disk, but this option would
435+
slow down writes on the service. This could go against other uses of the
436+
server.
437+
438+
When the Redis service is shared and used for multiple usages, locks could be
439+
removed by mistake.
440+
441+
.. caution::
442+
443+
The command ``FLUSHDB`` must not be called, or locks should be stored in a
444+
dedicated Redis service away from Cache.
445+
446+
CombinedStore
447+
~~~~~~~~~~~~~
448+
449+
Combined stores allow to store locks across several backends. It's a common
450+
mistake to think that the lock mechanism will be more reliable. This is wrong
451+
The ``CombinedStore`` will be, at best, as reliable as the least reliable of
452+
all managed stores. As soon as one managed store returns erroneous information,
453+
the ``CombinedStore`` won't be reliable.
454+
455+
.. caution::
456+
457+
All concurrent processes must use the same configuration, with the same
458+
amount of managed stored and the same endpoint.
459+
460+
.. tip::
461+
462+
Instead of using a cluster of Redis or Memcached servers, it's better to use
463+
a ``CombinedStore`` with a single server per managed store.
464+
465+
SemaphoreStore
466+
~~~~~~~~~~~~~~
467+
468+
Semaphores are handled by the Kernel level. In order to be reliable, processes
469+
must run on the same machine, virtual machine or container. Be careful when
470+
updating a Kubernetes or Swarm service because for a short period of time, there
471+
can be two running containers in parallel.
472+
473+
.. caution::
474+
475+
All concurrent processes must use the same machine. Before starting a
476+
concurrent process on a new machine, check that other process are stopped
477+
on the old one.
478+
479+
Overall
480+
~~~~~~~
481+
482+
Changing the configuration of stores should be done very carefully. For
483+
instance, during the deployment of a new version. Processes with new
484+
configuration must not be started while old processes with old configuration
485+
are still running.
486+
260487
.. _`locks`: https://en.wikipedia.org/wiki/Lock_(computer_science)
261488
.. _Packagist: https://packagist.org/packages/symfony/lock
262489
.. _`PHP semaphore functions`: http://php.net/manual/en/book.sem.php

components/property_info.rst

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,44 @@ provide whether properties are readable or writable as booleans.
237237
238238
The :class:`Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor` looks
239239
for getter/isser/setter method in addition to whether or not a property is public
240-
to determine if it's accessible. This based on how the :doc:`PropertyAccess </components/property_access>`
241-
works.
240+
to determine if it's accessible.
241+
242+
This is based on how :doc:`PropertyAccess </components/property_access>` works,
243+
so it even looks for adder/remover methods and can transform between singular
244+
and plural property names::
245+
246+
class SomeClass
247+
{
248+
private $analyses;
249+
private $feet;
250+
251+
public function addAnalyse(Dummy $analyse)
252+
{
253+
// ...
254+
}
255+
256+
public function removeAnalyse(Dummy $analyse)
257+
{
258+
// ...
259+
}
260+
261+
public function addFoot(Dummy $foot)
262+
{
263+
// ...
264+
}
265+
266+
public function removeFoot(Dummy $foot)
267+
{
268+
// ...
269+
}
270+
}
271+
272+
// to be writable, both the adder and the remover methods must be defined
273+
$propertyInfo->isWritable(SomeClass::class, 'analyses'); // returns true
274+
$propertyInfo->isWritable(SomeClass::class, 'feet'); // returns true
275+
276+
.. versionadded:: 3.2
277+
The support of adder/remover methods was introduced in Symfony 3.2.
242278

243279
.. tip::
244280

contributing/code/core_team.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ Active Core Members
8484
Form_, Serializer_, DependencyInjection_, and HttpKernel_ components;
8585

8686
* **Tobias Nyholm** (`Nyholm`) manages the official and contrib recipes
87-
repositories.
87+
repositories;
88+
89+
* **Samuel Rozé** (`sroze`_) can merge into Messenger_ component.
8890

8991
* **Deciders** (``@symfony/deciders`` on GitHub):
9092

@@ -192,6 +194,7 @@ discretion of the **Project Leader**.
192194
.. _Intl: https://github.com/symfony/intl
193195
.. _LDAP: https://github.com/symfony/ldap
194196
.. _Locale: https://github.com/symfony/locale
197+
.. _Messenger: https://github.com/symfony/messenger
195198
.. _MonologBridge: https://github.com/symfony/monolog-bridge
196199
.. _OptionsResolver: https://github.com/symfony/options-resolver
197200
.. _Process: https://github.com/symfony/process
@@ -227,3 +230,4 @@ discretion of the **Project Leader**.
227230
.. _`chalasr`: https://github.com/chalasr/
228231
.. _`ogizanagi`: https://github.com/ogizanagi/
229232
.. _`Nyholm`: https://github.com/Nyholm
233+
.. _`sroze`: https://github.com/sroze

controller/upload_file.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ Then, define a service for this class:
294294
use App\Service\FileUploader;
295295
296296
$container->autowire(FileUploader::class)
297-
->setArgument('$targetDir', '%brochures_directory%');
297+
->setArgument('$targetDirectory', '%brochures_directory%');
298298
299299
Now you're ready to use this service in the controller::
300300

@@ -454,7 +454,7 @@ controller.
454454
}
455455

456456
if ($fileName = $entity->getBrochure()) {
457-
$entity->setBrochure(new File($this->uploader->getTargetDir().'/'.$fileName));
457+
$entity->setBrochure(new File($this->uploader->getTargetDirectory().'/'.$fileName));
458458
}
459459
}
460460
}

deployment.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ How you set environment variables, depends on your setup: they can be set at the
135135
command line, in your Nginx configuration, or via other methods provided by your
136136
hosting service.
137137

138+
At the very least you need to define the ``SYMFONY_ENV=prod`` (or
139+
``APP_ENV=prod`` if you're using :doc:`Symfony Flex </setup/flex>`) to run the
140+
application in ``prod`` mode, but depending on your application you may need to
141+
define other env vars too.
142+
138143
C) Install/Update your Vendors
139144
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
140145

reference/forms/types/options/compound.rst.inc

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,22 @@ compound
33

44
**type**: ``boolean`` **default**: ``true``
55

6-
This option specifies if a form is compound. This is independent of whether
7-
the form actually has children. A form can be compound but not have any
8-
children at all (e.g. an empty collection form).
6+
If ``true`` this option creates the form as "compound", meaning that it
7+
can contain children and be a parent of other forms.
8+
9+
Most of the time you won't need to override this option.
10+
You might want to control for it when creating a custom form type
11+
with advanced rendering logic.
12+
13+
In a view a compound form is rendered as a ``<div>`` container or
14+
a ``<form>`` element (the whole form is obviously a compound form).
15+
16+
Non-compound forms are always leaves in a form tree, they cannot have children.
17+
18+
A non-compound form is rendered as one of the html form elements: ``<input>``
19+
(``TextType``, ``FileType``, ``HiddenType``), ``<textarea>`` (``TextareaType``)
20+
or ``<select>`` (``ChoiceType``).
21+
22+
An interesting case is the ``ChoiceType``. With ``expanded=false`` it is a non-compound form
23+
and is rendered as a ``<select>`` tag. With ``expanded=true`` the ``ChoiceType`` becomes a
24+
compound form and is rendered as a set of radios or checkboxes.

0 commit comments

Comments
 (0)