No Copy-Paste
Intro
Copy-pasting code is a bad habit. Once you copy-paste some code, you (should) assume the responsibility of constantly checking the source of copy-paste and apply the updates to where you pasted the code. This is especially troublesome when a project is updated. The core files are updated, but the copy->pasted code is not.
This page lists some easy ways of avoiding copy->paste.
JavaScript code
Assuming you are extending a JS file from core, e.g. wfed/dir/file
, you should import the core file and then extend it. The core file can still be identified by prefixing the file alias with _
: _wfed/dir/file
in our example.
Backbone classes
Backbone views/collections/modules can be extended like this:
define(['_wfed/dir/file'], function (BaseFile) {
return BaseFile.extend({
functionFromCore: function() {
const ret = BaseFile.prototype.functionFromCore.apply(this, arguments);
// your code here
return ret;
}
});
});
Note: Backbone classes have an extend
method. The code above is passing to it an object containing the new methods.
Note: The new method calls BaseFile.prototype.__NAME_OF_THE_METHOD__.apply(this, arguments)
to call the code from core.
Objects of functions
Some files in core are just objects of functions.
Example:
define([], function() {
return {
function1: function() {
// code here
},
function2() {
// code here
}
}
})
These can be extended like this:
define(['_wfed/dir/file'], function(BaseFile) {
return Object.assign({}, BaseFile, {
function2: function() {
const ret = BaseFile.prototype.function2.apply(this, arguments);
// your code here
return ret;
}
});
});
Note: The Object.assign
method is used to merge the core object with the new methods into a newly created object.
Note: The new method calls BaseFile.prototype.__NAME_OF_THE_METHOD__.apply(this, arguments)
to call the code from core. (Oh, the irony! This line in documentation was copy->pasted from the Backbone part 😄 Once you're in the code of the function, the method for calling code from core is the same.)
PHP code
Symfony Forms
v5+ Reordering core fields
One common reason for copy->pasting is the need to add a form field in a specific position. For this, one would usually copy->paste the entire form configuration from core and then add the new field in the desired position.
For Symfony forms, the wf_cms_admin.form.field_mover
service (alias Wf\Bundle\CmsBaseAdminBundle\Form\FormFieldMover
if using autowire) can be injected.
It provides a moveField
method that can be used to reorder fields in a form.
<?php
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Wf\Bundle\CmsBaseAdminBundle\Form\FormFieldMover;
class FormExtension extends AbstractTypeExtension
{
private $fieldMover;
public function __construct(FormFieldMover $fieldMover)
{
$this->fieldMover = $fieldMover;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('fieldToMove');
$builder->add('afterField');
$this->fieldMover->moveField($builder, 'fieldToMove', FormFieldMover::POSITION_AFTER, 'afterField');
// or
$this->fieldMover->moveField($builder, 'afterField', FormFieldMover::POSITION_FIRST);
// or
$this->fieldMover->moveField($builder, 'fieldToMove', FormFieldMover::POSITION_LAST);
}
public function __construct(
}
SonataAdmin
v5+ Reordering SonataAdmin form fields
Since v5.0
, XalokNext ships with WfSonataAdminTrait, a PHP Trait that helps with reordering fields in SonataAdminBundle forms. All admin classes defined in the core of XalokNext use this trait.
The trait provides a method moveFormField
that can be used to reorder fields in a form.
<?php
use Sonata\AdminBundle\Form\FormMapper;
use Wf\Bundle\CmsBaseAdminBundle\Form\FormFieldMover;
class MyAdmin extends Admin
{
use WfSonataAdminTrait;
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('fieldToMove');
$formMapper->add('afterField');
$this->moveFormField($formMapper, 'fieldToMove', FormFieldMover::POSITION_AFTER, 'afterField');
// or
$this->moveFormField($formMapper, 'afterField', FormFieldMover::POSITION_FIRST);
// or
$this->moveFormField($formMapper, 'fieldToMove', FormFieldMover::POSITION_LAST);
}
}
v5+ Reordering list (datagrid) fields
The trait also provides a method moveListField
that can be used to reorder fields in a list (datagrid).
<?php
use Sonata\AdminBundle\Datagrid\ListMapper;
use Wf\Bundle\CmsBaseAdminBundle\Form\FormFieldMover;
class MyAdmin extends Admin
{
use WfSonataAdminTrait;
protected function configureListFields(ListMapper $listMapper)
{
$listMapper->add('fieldToMove');
$listMapper->add('afterField');
$this->moveListField($listMapper, 'fieldToMove', FormFieldMover::POSITION_AFTER, 'afterField');
// or
$this->moveListField($listMapper, 'afterField', FormFieldMover::POSITION_FIRST);
// or
$this->moveListField($listMapper, 'fieldToMove', FormFieldMover::POSITION_LAST);
}
}
v7.1+Reordering SonataAdmin filter fields
Starting with v7.1
, the WfSonataAdminTrait
provides a method moveDatagridField
:
<?php
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Wf\Bundle\CmsBaseAdminBundle\Form\FormFieldMover;
class MyAdmin extends WfCmsBaseAdmin
{
use WfSonataAdminTrait;
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
parent::configureDatagridFilters($formMapper);
$datagridMapper->add('fieldToMove');
$datagridMapper->add('afterField');
$this->moveDatagridField($listMapper, 'fieldToMove', FormFieldMover::POSITION_AFTER, 'afterField');
// or
$this->moveDatagridField($listMapper, 'afterField', FormFieldMover::POSITION_FIRST);
// or
$this->moveDatagridField($listMapper, 'fieldToMove', FormFieldMover::POSITION_LAST);
}
}
v7.1+Adding options to SonataAdmin form fields
Starting with v7.1
, the WfSonataAdminTrait
provides a method mergeFormFieldOptions
:
<?php
use Sonata\AdminBundle\Form\FormMapper;
use Wf\Bundle\CmsBaseAdminBundle\Form\FormFieldMover;
class MyAdmin extends WfCmsBaseAdmin
{
use WfSonataAdminTrait;
protected function configureFormFields(FormMapper $formMapper)
{
parent::configureFormFields($formMapper);
$this->mergeFormFieldOptions($formMapper, 'fieldToChange', [
'row_attr' => [
'class' => 'wf-col-md-6'
]
]);
}
}
Symfony services
A common source of copy->pasting occurs when overwriting services defined either in wfcms/standard
or in third party bundles, when desiring to add an extra dependency and this is done through the __construct
method. It's likely that the service to be extended already has a list of dependencies in its constructor and this is copy->pasted to add the project's dependencies at the end.
The issue is that if the extended service needs a new dependency, its constructor signature will change, so the code in the project will fail. For this reason, prefer using setter injection instead of constructor injection.
If using Symfony's autowiring, you can use the @required
annotation to inject the service:
<?php
class MyService
{
private $x;
/**
* @required
*/
public function setX(X $x)
{
$this->x = $x;
}
}
If the dependency cannot be autowired, use the compiler pass to call this method:
<?php
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class MyCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$container->findDefinition('my_service')
->setClass(MyService::class)
->addMethodCall('setX', [new Reference('x')]);
}
}
Bundles configurations
Most of the bundles handle merging different configurations, so you don't need to copy->paste an existing configuration from wfcms/standard
(or soemewhere else) in order to add to it in the project.
For example, fos_elastica
indexes have some configuration in wfcms/standard.
# vendor/wfcms/standard/Wf/Bundle/CmsBaseBundle/Resources/config/third_party/fos_elastica.yml
wf_fos_multilanguage:
indexes:
"%fos_index_name%_image":
# ...
properties:
title: ~
tags:
property_path: tagsArray
tagsIds: ~
description: ~
createdAt:
type: date
source: ~
To add a custom property to the image index, there's no need to copy->paste the entire definition in the project, one should only add the desired properties:
# app/config/misc/fos_elastica.yml
wf_fos_multilanguage:
indexes:
"%fos_index_name%_image":
properties:
custom: ~
Notable exceptions
security.firewalls
and security.access_control
are not allowed to be merged. If one needs to add a new firewall
or a new access_control
, they need to copy->paste the configuration from wfcms/standard
.
Note: security.role_hierarchy
(and, generally, other security related configurations with the exception of the two above) CAN be merged, so one should not copy->paste them, but add to them or overwrite wfcms/standard
roles.