Architecture Overview - flagbit/Magento-FACTFinder GitHub Wiki
(Note: all of this information only applies to module version 3.4 and later)
So you want to dive into the module and wonder how all those models tie in together. But you don't want to read through all those lines of code to get the gist. That's what this page is for.
The Big Picture
To get all the blocks and templates within Magento to display all the FACT-Finder data, we use several layers of abstraction in between. In order of increasing abstraction they are:
- FACT-Finder's (XML) API
- The FACT-Finder PHP Framework
- The Facade Model
- The Handler Models
- Helpers, Blocks, Resource Models
The following sections will describe each of these in more or less detail to give you an overview where you will find which responsibilities.
FACT-Finder's (XML) API
The FACT-Finder API is just an HTTP API available in different formats. In particular we mostly use the XML version and in one case JSON. For more information on the API, see the FACT-Finder documentation.
The FACT-Finder PHP Framework
This is actually a separate library that can be used by any PHP store to ease the integration of FACT-Finder. It provides classes to access FACT-Finder's API (predominantly the XML interface) - so-called Adapters - and classes to represent the data that is returned by FACT-Finder. The framework can be found in its own repository. We won't go into much detail about it here, so check out that repository (literally) for more information.
For the module we made one customization to the framework. Namely, we introduced a new Configuration class, that links Magento's backend configuration with the framework.
The Facade Model
Now, this is actually part of the module. Specifically, this refers to the class Flagbit_FactFinder_Model_Facade. What it does is hide the framework specifics for the rest of the module (hence, the name). Most importantly, it holds all necessary Adapters and figures out when it can reuse them or when it has to provide a new one. It also allows other classes to configure those Adapters. In addition it bundles all the getters you can find on the Adapters (e.g. for the result of the SearchAdapter, for pushed products on a CampaignAdapter or for the items on a TagCloudAdapter). It also takes care of error handling.
The Handler Models
This is the last layer which turns all that FACT-Finder data into objects that can be used by Magento's blocks and helpers and so on. Thanks to the Facade's logic for reusing Adapters, there is no direct correlation between Handlers and Adapters. One Handler can use multiple Adapters and one Adapter can be used by multiple Handlers. This way Handlers can group functionality by one purpose, regardless of where the data is supplied by FACT-Finder.
The module ships with the following Handlers which all derive from an Abstract Handler:
SearchSecondarySearchSuggestTagCloudRecommendationsProductCampaign, subclassed by:ProductDetailCampaignShoppingCartCampaign
CheckStatus
This is the main place to add functionality. If you want to add a feature that represents a new way to process data delivered by FACT-Finder, go and add a new Handler class.
The Handlers are responsible for telling the Facade what they need. That is, which Adapters they need and how they should be configured. To do so, every Handler has to implement the abstract function configureFacade() which is called upon object creation. All configuration logic goes in here. If the Handler needs additional data, to accomplish that, create a new __construct() method that takes the data as an argument, and save it to private or protected member. Make sure that you call parent::__construct() afterward.
In addition the Handler can define some public methods that blocks and resource models can use to retrieve the processed data. In these methods you can get the data from the configured Adapters through the Facade's getters. Handlers never need to talk to Adapters directly.
There is one exception to the rule that all data is taken care of by a Handler. And that is FACT-Finder's tracking system (called SCIC), which is accessed through the framework's ScicAdapter. Due to the way this (rather unique) Adapter works at the moment, it did not seem practical to introduce a Handler in this case. However, the ScicAdapter is only used in very restricted places of the module (namely within the Processor and the Observer module whose scopes are very limited).
One more note on the usage of Handlers. At the moment all Handlers are used as Singletons (using Magento's built-in Mage::getSingleton). However, there is no strict requirement to do this. So if a new use-case would be easier to implement with multiple instances of a Handler, go ahead and do that.
Helpers, Blocks, Resource Models
Lastly, we have the code that directly ties into Magento's framework. In most cases these will be the Block classes. The separation of setting up the Facade and retrieving the data allows the rest of the code to fully leverage the framework's ability to send out requests in parallel. We just need to make sure that all Handlers are instantiated before the first one is used. This section is mostly about how to achieve that.
In some cases, you only need one Handler, which makes it pretty easy. For example the Backend helper uses a CheckStatus Handler to make sure that FACT-Finder can be queried with the given configuration. And that is the only Handler that will be used within this context. So it can simply be instantiated and used right away.
In the case of (for example) a regular frontend search result, things are bit more complicated. We surely need a Search Handler, but we might also want to use the SecondarySearch Handler, the TagCloud Handler and the Recommendation Handler. Moreover, we don't know in general which subset of Handlers we need for a certain frontend page. This will usually depend on which Blocks get rendered. Luckily, Magento traverses the layout tree twice. The first traversal calls _prepareLayout() on every Block that will be used on the page. This is where the initialization code goes. If you look at the Blocks that the module already defines, you will see that they initialize a Singleton instance of all Handlers they need and remember it for later use. Since nothing gets actually rendered during this step, there is no need to query any data from the Handlers before they are all set up. The second traversal is for rendering. Now we can get all the data we want from the Handlers. By keeping with this clear separation of set up and use of all Handlers, we can make full use of the parallel request, thus minimizing the network bottleneck and maximizing performance.
Now, there is one catch. There are a few cases where we do want to access a Handler's data before the rendering traversal. In particular, the module's Layer Block adds some children to itself depending on the After Search Navigation returned by the Search Handler (and it even adds the Slider Block, which will never be instantiated otherwise). There is one way to make sure such logic is executed after the completion of the _prepareLayout() traversal. Magento fires an event named controller_action_layout_generate_blocks_after after traversing for layout setup and before traversing for layout rendering. At this point all Handlers have been instantiated, so this does not break the parallel request functionality.
You might wonder that adding new Blocks will now call their _prepareLayout() again, which in turn might create new Handlers. However, we can safely assume that Blocks which are generated because of FACT-Finder results we already have will not require new data (and hence, new Handlers). Furthermore, if this was the case, we could not send all requests in parallel anyway, so this approach will still minimize the number of sequential requests.