This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
|
fulleron:module [2012/04/30 04:31] unirgy |
fulleron:module [2012/09/07 02:37] (current) unirgy |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Fulleron Modules ====== | ||
| + | This page describes standard steps of creating a module in Fulleron framework. | ||
| + | |||
| + | ===== Conventions ===== | ||
| + | |||
| + | Fulleron is built upon Buckyball library and inherently is not rigid in its structure or implementation. However, the following set of conventions is recommended for clean, uniform and maintainable application. | ||
| + | |||
| + | ==== Primary goal ==== | ||
| + | |||
| + | Achieve a maintainable balance between: | ||
| + | |||
| + | == Abstract concepts == | ||
| + | * Separation of concerns: files should be separated by their role in app | ||
| + | * Clear folder hierarchy: child entities should be under parent entities | ||
| + | == Hardware related limitations == | ||
| + | * Storage speed/ | ||
| + | * CPU/Storage limitation: optimize use of bytecode caching | ||
| + | == Immediate developer needs == | ||
| + | * Files should be intuitively found in folder structure | ||
| + | * Number of files open in IDE should be minimized | ||
| + | * Number of total files should be minimized (avoid "rice code") | ||
| + | * Reduce number of files with the same name (not an issue in some IDEs) | ||
| + | * Ease of debugging (resolving development errors) | ||
| + | * Some local configuration files should be shared between repo copies | ||
| + | == Future maintenance and flexibility == | ||
| + | * Ease of refactoring (renaming/ | ||
| + | * Ease of debugging (finding and resolving production errors) | ||
| + | * Internal extensibility (adding new features within module) | ||
| + | * External extensibility (adding/ | ||
| + | * Core and market module files should never be changed by developer | ||
| + | |||
| + | ==== Folder structure considerations ==== | ||
| + | |||
| + | The basic question whether files should be physically grouped by role (code, templates, css/js, etc) or bundle (independent feature set) has been definitely settled for the latter. The grouping by role is performed logically by the application (classes autoload, collection of views, collection of ''< | ||
| + | |||
| + | ==== File content and naming considerations ==== | ||
| + | |||
| + | === Classes === | ||
| + | |||
| + | == Multiple classes in the same file? == | ||
| + | While it is widely accepted to keep each class in a separate file, sometimes it makes more sense to keep many small classes within the same file. | ||
| + | |||
| + | Fulleron modules always load at least 1 class file, which contain a bootstrap method. If the module has a small bootstrap, model and controller, it makes sense to keep them in 1 file, so the filesystem will not have to waste time on loading many small files. | ||
| + | |||
| + | __NOTE: This should be used only during development (scaffolding new classes) or for very small modules to avoid wasting time on finding classes.__ | ||
| + | |||
| + | == Separate application areas (admin/ | ||
| + | In most cases bootstrap and configuration will differ significantly between admin and frontend areas, so it doesn' | ||
| + | This leads to splitting bootstrap files into '' | ||
| + | |||
| + | The bootstrap files can be named '' | ||
| + | |||
| + | If the module is small it might not make sense to separate classes/ | ||
| + | |||
| + | === Templates === | ||
| + | |||
| + | == Reasons to keep number of template files small == | ||
| + | * It becomes hard to know what is used when and where (large picture) | ||
| + | * Performance greatly suffers with multiple files | ||
| + | |||
| + | == Reasons to split templates into smaller chunks == | ||
| + | * No need to override huge template for a small change, opening up for issues with future upgrades | ||
| + | * Easier to read and understand a specific file (small picture) | ||
| + | |||
| + | ===== File structure ===== | ||
| + | |||
| + | ==== Anatomy of Fulleron application ==== | ||
| + | < | ||
| + | index.php | ||
| + | .htaccess | ||
| + | FCom/ # Core component modules. Can be located in shared location. | ||
| + | buckyball/ | ||
| + | com/ # Buckyball components | ||
| + | plugins/ | ||
| + | Core/ # Main module, initializes environment, | ||
| + | admin/ | ||
| + | index.php | ||
| + | .htaccess | ||
| + | api/ | ||
| + | index.php | ||
| + | .htaccess | ||
| + | tests/ | ||
| + | index.php | ||
| + | .htaccess | ||
| + | local/ | ||
| + | market/ | ||
| + | media/ | ||
| + | storage/ | ||
| + | .htaccess | ||
| + | cache/ | ||
| + | config/ | ||
| + | db.php | ||
| + | local.php | ||
| + | import/ | ||
| + | export/ | ||
| + | log/ | ||
| + | phptal/ | ||
| + | session/ | ||
| + | </ | ||
| + | |||
| + | ==== Anatomy of a module ==== | ||
| + | |||
| + | < | ||
| + | local/ | ||
| + | Vendor/ | ||
| + | manifest.(json|php) | ||
| + | Feature/ | ||
| + | manifest.(json|php) | ||
| + | Admin/ | ||
| + | Controller/ | ||
| + | Things.php | ||
| + | views/ | ||
| + | settings/ | ||
| + | Vendor_Feature.php | ||
| + | things/ | ||
| + | form-main.php | ||
| + | Frontend/ | ||
| + | css/ | ||
| + | js/ | ||
| + | views/ | ||
| + | Controller.php | ||
| + | Model/ | ||
| + | Thing.php | ||
| + | Admin.php | ||
| + | Frontend.php | ||
| + | Migrate.php | ||
| + | </ | ||
| + | |||
| + | ===== Manifests ===== | ||
| + | |||
| + | * Accepted manifest files are PHP or JSON files, which describe module aspects that are required to correctly identify, validate and load modules. | ||
| + | * A manifest file can describe a single or multiple modules. | ||
| + | * Modules within the same manifest can be enabled or disabled independently. | ||
| + | * Order of multiple modules within the same manifest is insignificant. | ||
| + | * Manifests can declare other folders where Fulleron should be scanning for modules (useful for vendor manifests). | ||
| + | |||
| + | ==== Manifest object structure ==== | ||
| + | |||
| + | === Sample from FCom === | ||
| + | <code php manifest.php> | ||
| + | <?php return array( | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ), | ||
| + | ), | ||
| + | ); | ||
| + | </ | ||
| + | === Sample from local dev === | ||
| + | <code javascript manifest.json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | " | ||
| + | }, | ||
| + | " | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | The '' | ||
| + | |||
| + | ===== Areas ===== | ||
| + | |||
| + | The website is logically split into areas of concern | ||
| + | |||
| + | Areas available by default: | ||
| + | * FCom_Frontend | ||
| + | * FCom_Admin | ||
| + | * FCom_Cron | ||
| + | * FCom_Api | ||
| + | |||
| + | To avoid wasting request time on bootstrapping initialization that is not required in every area, the bootstrap files are split in the way that makes sense for each specific module. | ||
| + | |||
| + | If a module has minimal or doesn' | ||
| + | Otherwise, it is recommended to keep initialization of Admin controllers, | ||
| + | The common initialization which is required for all areas, including areas unknown at the time of module development, | ||
| + | |||
| + | Area is defined by entry point script (index.php) and is accessible via '' | ||
| + | |||
| + | Examples of custom areas: | ||
| + | * Vendor_Dealer - 3rd party dealer control panel | ||
| + | ===== Bootstrap files ===== | ||
| + | |||
| + | Bootstrap files contain a class with at least one method ('' | ||
| + | |||
| + | __NOTE: Because the bootstrap method will be called always when the method is active, it should execute and exit quickly. Only system hook declarations should be called from bootstrap method.__ | ||
| + | |||
| + | On a slow dev server with debug log enabled the bootstrap should take less than 1ms and maximum 2-3ms. All the profiling is available in '' | ||
| + | |||
| + | All the bootstrap actions are logged, including in which module they happened, to help with debugging. | ||
| + | |||
| + | ==== System hook declarations ==== | ||
| + | |||
| + | === Routes === | ||
| + | |||
| + | Routes declare which action should be taken on match of a specific pattern of request method(s) and path(s). | ||
| + | |||
| + | The 1st parameter is route pattern, 2nd is the callback, and 3rd optional parameters. | ||
| + | Fulleron has a special notation for callbacks, used in events and routes, where class and method are separated by dot (.) This means that a method of a singleton will be executed. Alternative syntax is (->). | ||
| + | |||
| + | Controller actions are prepended by '' | ||
| + | |||
| + | <code php Vendor/ | ||
| + | // Example: | ||
| + | BFrontController:: | ||
| + | -> | ||
| + | -> | ||
| + | |||
| + | // Possibilities: | ||
| + | // Parameter is optional if last. | ||
| + | // Action methods other than GET should have suffix crud__POST, crud__PUT, crud__DELETE | ||
| + | -> | ||
| + | | ||
| + | // Multiple parameters, specific method, no need for prefix | ||
| + | -> | ||
| + | | ||
| + | // Required parameter, will not match without | ||
| + | -> | ||
| + | | ||
| + | // Wildcard, can include slashes | ||
| + | // Multiple handles of the same route can be declared, | ||
| + | // will be performed until not forwarded from within action | ||
| + | -> | ||
| + | -> | ||
| + | -> | ||
| + | -> | ||
| + | | ||
| + | // Call any controller action, specified by parameter | ||
| + | -> | ||
| + | | ||
| + | // Regexp route match (starts with ^) | ||
| + | -> | ||
| + | ; | ||
| + | </ | ||
| + | |||
| + | === Views === | ||
| + | |||
| + | Each module contains all its views (templates) within module folder. Views are separated by application area (Admin, Frontend). | ||
| + | |||
| + | <code php Vendor/ | ||
| + | // register all views (templates) from folder Frontend/ | ||
| + | BLayout:: | ||
| + | </ | ||
| + | |||
| + | The views are accessible via '' | ||
| + | |||
| + | The previous example will reference view '' | ||
| + | |||
| + | The file extension will define which renderer will be used to render the template file. '' | ||
| + | |||
| + | PHPTAL is currently the default renderer for CMS pages and blocks, with some custom integrations with Fulleron. | ||
| + | |||
| + | It is also possible to decare individual views instantiated not from default class (other doc scope). | ||
| + | |||
| + | === Event Observers === | ||
| + | |||
| + | <code php Vendor/ | ||
| + | public static function bootstrap() | ||
| + | { | ||
| + | //... | ||
| + | BPubSub:: | ||
| + | // Usual syntax | ||
| + | -> | ||
| + | // Singleton instance method callback | ||
| + | -> | ||
| + | ; | ||
| + | } | ||
| + | |||
| + | public function onProductSaveAfter($args) | ||
| + | { | ||
| + | $product = $args[' | ||
| + | // do something with $product model | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Themes/ | ||
| + | |||
| + | <code php Vendor/ | ||
| + | public static function bootstrap() | ||
| + | { | ||
| + | // add/update module related layouts | ||
| + | BLayout:: | ||
| + | } | ||
| + | |||
| + | public static function layout() | ||
| + | { | ||
| + | BLayout:: | ||
| + | // update base layout initialization, | ||
| + | ' | ||
| + | array(' | ||
| + | array(' | ||
| + | array(' | ||
| + | array(' | ||
| + | array(' | ||
| + | array(' | ||
| + | | ||
| + | array(' | ||
| + | array(' | ||
| + | )), | ||
| + | array(' | ||
| + | array(' | ||
| + | )), | ||
| + | ), | ||
| + | // update a specific page identified by url. The convention is if it starts with slash it's a page, otherwise it has a special use | ||
| + | '/ | ||
| + | array(' | ||
| + | array(' | ||
| + | array(' | ||
| + | )), | ||
| + | array(' | ||
| + | ), | ||
| + | )); | ||
| + | } | ||
| + | </ | ||
| + | === Class overrides === | ||
| + | All classes that inherit from BClass or BModel can be overridden, and any use that starts with OriginalClass:: | ||
| + | |||
| + | <code php AugmentedClass.php> | ||
| + | class AugmentedClass extends OriginalClass | ||
| + | { | ||
| + | |||
| + | } | ||
| + | |||
| + | BClassRegistry:: | ||
| + | </ | ||
| + | |||
| + | NOTE: If you wish to have IDE autocompletion, | ||
| + | <code php OriginalClass.php> | ||
| + | /** | ||
| + | * Shortcut to help with IDE autocompletion | ||
| + | * | ||
| + | * @return OriginalClass | ||
| + | */ | ||
| + | public static function i($new=false, | ||
| + | { | ||
| + | return BClassRegistry:: | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === Other Initializations === | ||
| + | |||
| + | ===== Layouts ===== | ||
| + | |||
| + | === Directives === | ||
| + | |||
| + | <code php> | ||
| + | // layout directives structure: | ||
| + | // first item is directive type, 2nd is item reference, after that are commands | ||
| + | array( | ||
| + | // these are the available directives: | ||
| + | // set root view. this view will be the one loaded on response, and call other views | ||
| + | array(' | ||
| + | | ||
| + | // load another layout | ||
| + | array(' | ||
| + | | ||
| + | // add views to a hook, in a view: < ?php echo $this-> | ||
| + | array(' | ||
| + | | ||
| + | // perform a method or set variable to a view | ||
| + | array(' | ||
| + | ' | ||
| + | ' | ||
| + | ' | ||
| + | ), | ||
| + | ' | ||
| + | array(' | ||
| + | array(' | ||
| + | ), | ||
| + | ), | ||
| + | | ||
| + | // perform an arbitrary callback | ||
| + | array(' | ||
| + | ) | ||
| + | </ | ||
| + | |||
| + | ===== Models ===== | ||
| + | <code php Vendor/ | ||
| + | class Vendor_Feature_Model_Item extends FCom_Core_Model_Abstract | ||
| + | { | ||
| + | // declare table name | ||
| + | protected static $_table = ' | ||
| + | // declare original class for event names, in case there' | ||
| + | protected static $_origClass = __CLASS__; | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== Controllers ===== | ||
| + | ==== Frontend ==== | ||
| + | <code php Vendor/ | ||
| + | class Vendor_Feature_Frontend_Controller_Items extends FCom_Frontend_Controller_Abstract | ||
| + | { | ||
| + | public function action_index() | ||
| + | { | ||
| + | $this-> | ||
| + | } | ||
| + | | ||
| + | public function action_index__POST() | ||
| + | { | ||
| + | //... | ||
| + | BResponse:: | ||
| + | } | ||
| + | | ||
| + | public function action_json() | ||
| + | { | ||
| + | $request = BRequest:: | ||
| + | $response = array(/ | ||
| + | BResponse:: | ||
| + | } | ||
| + | | ||
| + | public function action_json__POST() | ||
| + | { | ||
| + | $request = BRequest:: | ||
| + | $response = array(/ | ||
| + | BResponse:: | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== Admin ==== | ||
| + | <code php Vendor/ | ||
| + | class Vendor_Feature_Admin_Controller_Items extends FCom_Admin_Controller_Abstract | ||
| + | { | ||
| + | protected static $_permission = ' | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== DB Migration ===== | ||
| + | |||
| + | ==== Suggested Usage ==== | ||
| + | <code javascsript manifest.json> | ||
| + | { | ||
| + | " | ||
| + | " | ||
| + | /*...*/ | ||
| + | " | ||
| + | /*...*/ | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | <code php Vendor/ | ||
| + | <?php | ||
| + | class Vendor_Feature_Model_Thing extends FCom_Core_Model_Abstract | ||
| + | { | ||
| + | protected static $_table = ' | ||
| + | | ||
| + | /*...*/ | ||
| + | | ||
| + | public static function install() | ||
| + | { | ||
| + | BDb:: | ||
| + | CREATE TABLE IF NOT EXISTS " | ||
| + | /* ... */ | ||
| + | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; | ||
| + | "); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | <code php Vendor/ | ||
| + | <?php | ||
| + | class Vendor_Feature_Migrate extends BClass | ||
| + | { | ||
| + | public function run() | ||
| + | { | ||
| + | BMigrate:: | ||
| + | BMigrate:: | ||
| + | } | ||
| + | | ||
| + | public function install() | ||
| + | { | ||
| + | Vendor_Feature_Model_Thing:: | ||
| + | BDb:: | ||
| + | // | ||
| + | "); | ||
| + | } | ||
| + | | ||
| + | public function upgrade_0_1_1() | ||
| + | { | ||
| + | BDb:: | ||
| + | // | ||
| + | " | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | The recommended practice is to keep one install method for the latest version (for new installations), | ||
| + | |||
| + | If you run any ORM operations within migration scripts after updating table structure, reset DDL cache: '' | ||