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/22 07:43] 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: '' |