Migration from 1.13 to 1.14

This page describes the necessary adaption of your software to be compatible with release 1.14 of the APF.

1. Initialisation of the GORM using the DIServiceManager

The o/r mapper Generic OR mapper can now natively be created with the DIServiceManager. You can take a complete application sample from Erzeugen des GORM mit dem DIServiceManager (German).

This adaption is optional, since the the o/r mapper can be created by the GenericORMapperFactory.

2. Relation table layout of the GORM

As of release 1.14 self-reference is possible. This implies an adaption of the relation table layout regarding the identification of the source and target objects. For this reason, the column refering to the source object das been added the Source_ prefix and the target object column is now prepended the Target_.

Since the fact, that this change is not backward-compatible these changes must be applied to all existing database tables belonging to a GORM setup. Each column including a source object id, the name must be prepended Source_ each column referring to a target object must be prefixed with Target_.

2.1. User management module

As an application sample, you can take the User management module shipped with the APF. This module includes the subsequent relation definitions:

APF-Konfiguration
[Application2Group] Type = "COMPOSITION" SourceObject = "Application" TargetObject = "Group" [Group2User] Type = "ASSOCIATION" SourceObject = "Group" TargetObject = "User" [Role2User] Type = "ASSOCIATION" SourceObject = "Role" TargetObject = "User" [Role2PermissionSet] Type = "ASSOCIATION" SourceObject = "Role" TargetObject = "PermissionSet" [Application2User] Type = "COMPOSITION" SourceObject = "Application" TargetObject = "User" [Application2Role] Type = "COMPOSITION" SourceObject = "Application" TargetObject = "Role" [Application2PermissionSet] Type = "COMPOSITION" SourceObject = "Application" TargetObject = "PermissionSet" [PermissionSet2Permission] Type = "ASSOCIATION" SourceObject = "PermissionSet" TargetObject = "Permission" [Application2Permission] Type = "COMPOSITION" SourceObject = "Application" TargetObject = "Permission" [Application2AppProxy] Type = "COMPOSITION" SourceObject = "Application" TargetObject = "AppProxy" [Application2AppProxyType] Type = "COMPOSITION" SourceObject = "Application" TargetObject = "AppProxyType" [AppProxy2User] Type = "ASSOCIATION" SourceObject = "AppProxy" TargetObject = "User" [AppProxy2Group] Type = "ASSOCIATION" SourceObject = "AppProxy" TargetObject = "Group" [AppProxy2AppProxyType] Type = "ASSOCIATION" SourceObject = "AppProxy" TargetObject = "AppProxyType"

Applying the above rule you have to apply the following update statements to your user management tables:

SQL-Statement
ALTER TABLE `ass_appproxy2appproxytype` CHANGE `AppProxyID` `Source_AppProxyID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `AppProxyTypeID` `Target_AppProxyTypeID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `ass_appproxy2group` CHANGE `AppProxyID` `Source_AppProxyID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `GroupID` `Target_GroupID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `ass_appproxy2user` CHANGE `AppProxyID` `Source_AppProxyID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `UserID` `Target_UserID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `ass_group2user` CHANGE `GroupID` `Source_GroupID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `UserID` `Target_UserID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `ass_permissionset2permission` CHANGE `PermissionSetID` `Source_PermissionSetID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `PermissionID` `Target_PermissionID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `ass_role2permissionset` CHANGE `RoleID` `Source_RoleID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `PermissionSetID` `Target_PermissionSetID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `ass_role2user` CHANGE `RoleID` `Source_RoleID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `UserID` `Target_UserID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `cmp_application2appproxy` CHANGE `ApplicationID` `Source_ApplicationID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `AppProxyID` `Target_AppProxyID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `cmp_application2appproxytype` CHANGE `ApplicationID` `Source_ApplicationID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `AppProxyTypeID` `Target_AppProxyTypeID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `cmp_application2group` CHANGE `ApplicationID` `Source_ApplicationID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `GroupID` `Target_GroupID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `cmp_application2permission` CHANGE `ApplicationID` `Source_ApplicationID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `PermissionID` `Target_PermissionID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `cmp_application2permissionset` CHANGE `ApplicationID` `Source_ApplicationID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `PermissionSetID` `Target_PermissionSetID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `cmp_application2role` CHANGE `ApplicationID` `Source_ApplicationID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `RoleID` `Target_RoleID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'; ALTER TABLE `cmp_application2user` CHANGE `ApplicationID` `Source_ApplicationID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `UserID` `Target_UserID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0';

2.2. Guestbook 2009

As a further example the guestbook's table setup is used. The module (guestbook2009) is shipped with the APF as well. It contains the following relation definitions:

APF-Konfiguration
[Guestbook2LangDepValues] Type = "COMPOSITION" SourceObject = "Guestbook" TargetObject = "Attribute" [Entry2LangDepValues] Type = "COMPOSITION" SourceObject = "Entry" TargetObject = "Attribute" [Guestbook2Adminstrator] Type = "ASSOCIATION" SourceObject = "Guestbook" TargetObject = "User" [Editor2Entry] Type = "ASSOCIATION" SourceObject = "User" TargetObject = "Entry" [Guestbook2Entry] Type = "COMPOSITION" SourceObject = "Guestbook" TargetObject = "Entry" [Attribute2Language] Type = "ASSOCIATION" SourceObject = "Attribute" TargetObject = "Language"

Updating the module to release 1.14 you have to apply the subsequent update statements to your guestbook module database:

SQL-Statement
ALTER TABLE `ass_attribute2language` CHANGE `AttributeID` `Source_AttributeID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `LanguageID` `Target_LanguageID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0' ALTER TABLE `ass_editor2entry` CHANGE `UserID` `Source_UserID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `EntryID` `Target_EntryID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0' ALTER TABLE `ass_guestbook2adminstrator` CHANGE `GuestbookID` `Source_GuestbookID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `UserID` `Guestbook_UserID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0' ALTER TABLE `cmp_entry2langdepvalues` CHANGE `EntryID` `Source_EntryID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `AttributeID` `Target_AttributeID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0' ALTER TABLE `cmp_guestbook2entry` CHANGE `GuestbookID` `Source_GuestbookID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `EntryID` `Target_EntryID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0' ALTER TABLE `cmp_guestbook2langdepvalues` CHANGE `GuestbookID` `Source_GuestbookID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0', CHANGE `AttributeID` `Target_AttributeID` INT( 5 ) UNSIGNED NOT NULL DEFAULT '0'

3. API

Release 1.14 includes API clean-ups and optimizations that are necessary to keep the Adventure PHP Framework clean and understandable and to prepare the code for new features. The next chapters describe the API changes that directly or indirectly affect your software components.

3.1. Deprecated methods

The following methods have been marked as deprecated and have now been replaced by the methods noted in brackets:

  • APFObject::__getAttributesAsString() (APFObject::getAttributesAsString())
  • APFObject::__getServiceObject() (APFObject::getServiceObject())
  • APFObject::__getAndInitServiceObject() (APFObject::getAndInitServiceObject())
  • APFObject::__getDIServiceObject() (APFObject::getDIServiceObject())
  • base_controller::__getForm() (base_controller::getForm())
  • base_controller::__getTemplate() (base_controller::getTemplate())
  • base_controller::__placeholderExists() (base_controller::placeHolderExists())
  • base_controller::__templatePlaceholderExists() (base_controller::templatePlaceHolderExists())
  • iteratorBaseController::__getIterator() (iteratorBaseController::getIterator())

These methods will be completely removed from the APF code base in 1.15.

In order to ease migration, using the above methods will result in a deprecated warning. Using the stack trace you can easily find the affected lines of code.

3.2. Refactoring

Due to consistency reasons several methods and properties have been moved from APFObject to Document. The reasons for this change is the correlation of an object either to an APF service (service-orientation) or to a DOM object (e.g. UI elements). This separation has not been applied to the code as desired in the past.

The following attributes now reside within the Document class and their children as well:

  • __ObjectID
  • __Children
  • __ParentObject

In case these attributes are included in any __sleep() or __wakeup() method those object will not be serializable any more. For this reasons, they must be removed from services. This is especially important for SessionSingleton objects created with the ServiceManager.

4. ServiceManager

The ServiceManager can be accessed statically starting with release 1.14. This change has been introduced due to performance and access advantages.

Since 1.14, you may access your service implementations using the APFObject::getServiceObject() and APFObject::getAndInitServiceObject() methods as well as noted within the following code box:

PHP-Code
$service = ServiceManager::getServiceObject( 'my::namespace', 'ClassNameOfService', 'application-context', 'de', APFObject::SERVICE_TYPE_SINGLETON ); $initializedService = ServiceManager::getAndInitServiceObject( 'my::namespace', 'ClassNameOfService', 'application-context', 'de', $initParam, APFObject::SERVICE_TYPE_SINGLETON );

5. MySQLiHandler

The database driver MySQLiHandler now throws exceptions instead of errors facing a connection issue. This eases handling errors within your source code. Updating to 1.14 you need to add catch blocks handling the DatabaseHandlerException exception to initiate error handling.

6. Domain objects with the GORM

The generic o/r mapper of the APF now includes the domain object feature. Domain objects can be specified as object and relation definition. The GORM also includes a tool to create and update the class definitions.

The discussion concerning this feature can be read about in the Forum (German).

7. Link generation

The link generation has been completely redesigned in 1.14. The LinkHandler and FrontcontrollerLinkHandler are still shipped with the APF but the internal functionality has been changed to use the new mechanism. Both components will be removed in version 1.15.

7.1. FrontcontrollerLinkHandler

The changes noted above mean, that the method FrontcontrollerLinkHandler::generateURLParams() is no more available in 1.14. The internal functionality has been replaced by the link schemes. Generating action links using

PHP-Code
import('tools::link', 'FrontcontrollerLinkHandler'); ... $actionParams = array('image' => $image, 'ext' => $ext, 'path' => 'MediaPath', 'size' => $matches[2]); $params = FrontcontrollerLinkHandler::generateURLParams('3rdparty::imageresizer', 'showImage', $actionParams); $urlBasePath = Registry::retrieve('apf::core', 'CurrentRequestURL'); $url = FrontcontrollerLinkHandler::generateLink($urlBasePath, $params);

must be replaced by

PHP-Code
import('tools::link', 'LinkGenerator'); ... $urlBasePath = Registry::retrieve('apf::core', 'CurrentRequestURL'); $url = LinkGenerator::generateActionUrl(Url::fromString($urlBasePath), '3rdparty::imageresizer', 'showImage', array( 'image' => $image, 'ext' => $ext, 'path' => 'MediaPath', 'size' => $matches[2] ));

in 1.14.

7.2. Common usage

As you can take from the last chapter, the link generation is now done by the LinkGenerator class. For this reason, you should switch from LinkHandler or FrontcontrollerLinkHandler to the new mechanism. In 1.15 all occurrences must be migrated anyway because they are removed from the APF.

Generating a url using the LinkHandler for releases <= 1.13 like

PHP-Code
$url = LinkHandler::generateLink($_SERVER['REQUEST_URI'], array('gbview' => 'admindelete', 'entryid' => $entryId));

can now be done as follows:

PHP-Code
$url = LinkGenerator::generateUrl(Url::fromCurrent()->mergeQuery(array('gbview' => 'admindelete', 'entryid' => $entryId)));

Further methods of the Url can be taken from the API documentation.

Generating action urls in releases <= 1.13 is as follows:

PHP-Code
$namespace = str_replace('::', '_', $namespace); if ($urlRewriting) { $actionParam = array( 'extensions_jscssinclusion_biz-action/sGCJ' => 'path/' . $namespace . '/type/' . $type . '/file/' . $filename ); } else { $actionParam = array( 'extensions_jscssinclusion_biz-action:sGCJ' => 'path:' . $namespace . '|type:' . $type . '|file:' . $filename ); } $url = FrontcontrollerLinkHandler::generateLink($url, $actionParam);

In 1.14 this is more convenient:

PHP-Code
$url = LinkGenenerator::generateActionUrl(Url::fromString($url), 'extensions::jscssinclusion::biz', 'sGCJ', array( 'path' => str_replace('::', '_', $namespace), 'type' => $type, 'file' => $filename ));

Further examples can be taken from the documentation under Links.

8. Front controller

8.1. Common configuration

In line with the refactorings within release 1.14 the configuration attributes FC.ActionFile and FC.InputFile have been removed. This is information is already contained in FC.ActionClass as well as FC.InputClass.

This change allows to force consistent addressing of action implementation using namespace and class name.

8.2. Input classes

In 1.14 you are no more forced to implement a special input class for a front controller action. In the past, you were advised to create a stub file at least to not break the convention. In case the FC.InputClass is present within the configuration, the referred class is used. Otherwise, the FrontcontrollerInput is applied the action parameters. As of 1.14, a minimal action configuration is as follows:

APF-Konfiguration
[showCaptcha] FC.ActionNamespace = "modules::captcha::biz::actions" FC.ActionClass = "ShowCaptchaImageAction" FC.InputParams = ""

8.3. Action classes

Along with the attribute refactoring described in chapter 3.2. all usages of $this->__ParentObject have been removed. This property contained a reference to the current instance of the front controller until release 1.13. As of revision 1.14 the current instance of the front controller can be retrieved by the AbstractFrontcontrollerAction::getFrontController() method.

8.4. Bootstrap file

Release 1.14 introduces a revised input and output filter concept. The first part of the changes apply to the filter execution that is done by the front controller only. The reason for this change is that the front controller is now the leading component concerning the request processing of the APF. The page controller now is only responsible for DOM tree management as it should have been in the past (separation of concerns).

For this reason, migration to 1.14 includes changes to the bootstrap files (index.php) to delegate the request processing to the front controller instead of to the page controller.

In case this change is not applied to your code stack, filters are no more executed. This may lead to unexpected behaviour. In case your application already uses the front controller, this adaption is not necessary.

In case you have a bootstrap file like this

PHP-Code
include_once('./apps/core/pagecontroller/pagecontroller.php'); $page = new Page(); $page->loadDesign('custom-modules::calc::pres::templates', 'calc'); echo $page->transform();

you must replace it with something like this:

PHP-Code
include_once('./apps/core/pagecontroller/pagecontroller.php'); import('core::frontcontroller', 'Frontcontroller'); $fC = &Singleton::getInstance('Frontcontroller'); echo $fC->start('custom-modules::calc::pres::templates', 'calc');

The configuration of the application's context and language is identical to the page controller:

PHP-Code
include_once('./apps/core/pagecontroller/pagecontroller.php'); import('core::frontcontroller', 'Frontcontroller'); $fC = &Singleton::getInstance('Frontcontroller'); $fC->setContext('...'); $fC->setLanguage('...'); echo $fC->start('custom-modules::calc::pres::templates', 'calc');

9. User management

In release 1.14 the password saving mechanism of the user management module has been switched to a more secure process. The md5 algorithm used in releases before 1.14 is prone to brute force or rainbow table attacks. For this reason, a new PasswordHashProvider has been implemented that uses the crypt() function in conjunction with a static and dynamic salt (user dependent). Please note, that you are still allowed to specify the old one but we strongly recommend to use the new provider. To ease migration, a mechanism has been implemented that automatically migrates old accounts to the new hash algorithm on-the-fly and during normal operation of your application.

Because the new mechanism uses a static salt, each application should contain a definition. In case no salt is specified with the *_umgtconfig.ini the APF's default salt is used. To set a dedicated salt, please add

APF-Konfiguration
Salt = ""

to your user management configuration. It is recommended to use a salt including special characters.

Loss or change of the static salt results to the situation that all passwords stored within the database are useless. Already registered users cannot log in any more. Thus, a backup of the salt configuration is strongly recommended!

In order to migrate an application from the old hash algorithm to the new mechanism two password hash providers must be specified within the configuration. The first provider is regarded as the "new" one, all others are considered als fallback providers. The naming of the subsections is user-defined only the order is relevant.

APF-Konfiguration
PasswordHashProvider.Default.Namespace = "modules::usermanagement::biz::provider::crypt" PasswordHashProvider.Default.Class = "CryptHardcodedSaltPasswordHashProvider" PasswordHashProvider.Fallback.Namespace = "modules::usermanagement::biz::provider::md5" PasswordHashProvider.Fallback.Class = "OldMD5PasswordHashProvider"

Having such a configuration the user management's business component tries to login the user with the credentials passed to the method call using the first provider. In case this fails, all other providers are used to log in the user. In case the manager succeeds the password is accepted and updated to the algorithm of the first provider automatically. After a short period of time all users having accessed the application are updated to the new hash algorithm.

The name of the provider (here Default und Fallback) are selected by accident and do not follow any naming convention. You can thus select any name for the provider definitions above or further providers.

Further, storing the dynamic salt requires an additional column within the user table and the user management's objection definition for the generic o/r mapper. The dynamic salt is generated individually for each user.

To update your configuration, please add

APF-Konfiguration
DynamicSalt = "VARCHAR(50)"

to your *_umgt_objects.ini at the User section. Moreover, the attribute must be dded to the ent_user table. This can be done manually or using the GORM update tool.

Manually updating your table setup, please use the following statement:

SQL-Statement
ALTER TABLE ent_user ADD `DynamicSalt` varchar(50) NOT NULL DEFAULT '' AFTER `Password`;

10. Page controller

Due to naming consistency reasons addressing a (Document-)Controller within a template file has been changed to namespace-and-class-name-only. In case you have different specifications of controller class and file these differences must be cleaned up migrating to release 1.14.

Definitions like

APF-Template
<@controller namespace="my::namespace::pres::controller" file="bar_controller" class="foo_controller" @>

must be changed to

APF-Template
<@controller namespace="my::namespace::pres::controller" class="foo_controller" @>

If necessary, the file including the controller implementation (here: bar_controller.php) must be renamed to foo_controller.php.

This change has been implemented in a backward-compatible way. This means, that the functionality to specify files that are named differently to the controller class name is still working in 1.14. Please note, that the functionality is removed in 1.15. For this reason, it is recommended to apply this migration step switching to 1.14 already.

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
There are no comments belonging to this article.