Wiki

Clone wiki

Oroboros Core / Home

Oroboros Core

#Oroboros Core


Current Release: 0.2.4

Author: Brian Dayhoff

Copyright: Copyright 2016, all rights reserved.

License: MIT


    #About

    The goal of Oroboros core is to function as an interoperable MVC toolbox.

    This library provides an extensive MVC framework, but presents it as an unopinionated library of tools.

    This system does not provide any opinion about what ought to happen anywhere outside of it's own scope. It provides you tools that can be leveraged to complete baseline requirements of a mature web or cgi application, but does not inherently enforce any specific order of operations. Fully leveraging the supplied system requires some adherence to it's architectural approach, but all parts can be pieced out and used to fulfill quick operations without any unexpected operations.

    The underlying goal of this project is to provide all of the tools that you need, and none of the opinion about when or where to use them.

    ##Background##

    This package is a refactor of a longstanding private project of mine. I had originally built a CMS that used this as it's core logic, with the intention of making it interoperable with other CMS's. The goal was to open it up as an open source project when it was stable enough to operate well with existing commonplace CMS's (Wordpress, Joomla, Magento, etc). It occurred to me deep into development, that the same interoperability problem also frequently occurs in frameworks, and the biggest complaint that both I have had working with them as well as have most commonly heard from peer developers, is how most systems try to think too much for you and wind up locking you into a specific practice that doesn't always fit with real world considerations.

    So that was about when I decided to gut the underlying system out of the CMS, and provide it as an opinionless toolbox. I still intend to complete the CMS as well, but felt that the core architecture was equally useful as a standalone package, so I divided the two to entirely separate the opinion from the work. I am currently porting the majority of this architecture into this package, and will provide another package for the CMS whenever I get around to releasing it (which will use this as a dependency).

    ##Project Goal##

    The goal of this package is to provide an unopinionated (or as close to it as feasible) MVC architecture that is interoperable with pretty much anything else without too much legwork.

    It is designed so that most of it's classes can be swapped out for any of your own that honor the same api, which can be done at either a package level (like swapping in monolog for the existing logger seamlessly, or guzzle for the existing message system seamlessly), or at the individual class level as well.

    It also has absolutely no class inheritance requirements whatsoever, at any place throughout it's architecture, using classes only as containers that combine a contract to honor (an interface) with a signature that does (a trait). For more information on this approach, please see our Design Specifications for an overview of the structure of this approach.

    In contrast to other MVC frameworks, this one does not decide how to do it for you at all. It presents a number of possible answers (usually with a default that can be overridden easily), and gives you a way to extend your own answer pretty easily if none of those fit, regardless of what part of the platform it applies to. It borrows a lot of the prototypical object orientation approach from javascript, but also leverages the advantages of class visibility and interface enforcement that PHP has to keep things enforced in the proper scope. This package is not an out-of-the-box site. It requires a small degree of legwork to produce a site (but not much), however it maximizes the flexibility of forward scalability and interoperability, and relies on you, the developer, to pick the best available strategy or provide your own and run with it instead of picking the only strategy because that's just how it works. Having worked a number of times in the wild maintaining legacy codebases, the lack of this flexibility in the underlying system architecture seems primarily to be the limiting factor for ongoing maintenance, scalability, and much earlier end-of-life terms of software (or alternately relying on deprecated components forever because you can't leverage a better option). We all know "frameworks suck", so this is much more a library that incidentally can be used as a framework than a framework that has to be used to leverage it's libraries.

    ##Standards##

    Please see our extended documentation on Interoperability and Internal Standards for extensive support and integration policies, as well as an overview of our own internal standards.

    Please note that our internal standards SHALL NOT be enforced for 3rd party useage, UNLESS failing to honor them would make your work inherently inconsistent with our architecture.

    Contributors to Oroboros core, as well as related Oroboros package releases are expected to honor all internal standards explicitly within the scope of their contributing work.

    End users are expected to follow only the conventions that are listed under the guidelines page, and can otherwise consider our internal standards to be un-enforced best practices within the scope of their own useage. We have made considerable effort to keep 3rd party useage as flexible and straightforward as possible, which is part of the reason we enforce such explicit standards for internal development. Developers independently using this library SHOULD NOT assume they are held to our internal standards unless they wish to contribute to core development.

    ###Universal Standards###

    This project will maintain adherence to all accepted PSR standards that supply an interface to maximize interoperability. Psr-1 and Psr-2 are not directly enforced, because they do not provide interfaces. Psr-0 is deprecated, and is not supported (use Psr-4 instead). Psr-4 is available and enforced despite not providing an interface (because it doesn't work if you don't follow the standard anyhow).

    Currently accepted Psr's can be seen here.

    The following Psr's that provide interfaces will be directly enforced in their own scope throughout this project:

    • Psr-3
    • Psr-6
    • Psr-7
    • Psr-11
    • Psr-13

    Psr-16 (the last accepted Psr as of this writing) will be integrated after Psr-6 is fully integrated, as it is an extension of that.

    Further Psr's that reach acceptance will be integrated at the earliest convenience as long as they provide an interface, or can be realistically enforced within the codebase.

    ###Internal Interoperability Standards###

    This MVC architecture is designed with interoperability in mind. For that reason, it is structured to provide a means for any of it's class types to be interchangeable with other systems as much as possible.

    To accomplish this end, an interface-driven api meta-system is put in place throughout the architecture to allow classes from within and outside it's architecture to be used interchangeably. This bears some passing similarity to Lua's metatables, if you are familiar with them.

    The architecture follows the following format to this end:

    All concrete classes within the system MUST provide an api, which is an interface that declares constants making it recognizeable to the system, and defining it's scope of operation.

    The currently available interfaces to this end are available in the src/core/interfaces/api directory. You can roll your own also, and provided they declare the expected constants at least with their default values, they are then interchangeable.

    These interfaces should be bound to a concrete class, which will make them then interchangeable with Oroboros core classes provided they adhere to their interface. As constants cannot be overridden in interfaces, there is no specific interface they can be extended from to fulfill this task. Expected constants are used in place of this to resolve ambiguity.

    To do so, you will need to register your class with the Codex, which tracks what the top-most override available in the current scope is, and then informs the Prototyper and Factory to favor that class when instantiating new instances.

    Some api's are complex, and difficult to honor completely by manual effort.

    For this reason, the following approach to the architecture is taken to circumvent extension difficulty:

    • All overrideable classes will inherit their methods from a trait, and will not provide their own methods whenever possible.
    • Traits will be written to satisfy specific Api's, and bound to the classes that are expected to honor them.
    • Classes will focus on organizing the required traits in their own scope, and declaring inheritance, interfaces, and constants as needed to satisfy their Api.
    • For this reason, classes should generally be considered more to be containers than to directly provide functionality.
    • Abstract classes do not implement their Api directly, even if they satisfy it.
    • Concrete classes that extend abstract classes implement their Api interface directly. This prevents collisions due to constants declared in the Api that cannot be overridden.
    • The supplied concrete classes are all final, and add no additional functionality beyond their abstract, unless they are internal or very case specific.
    • This allows you to use the required traits on any 3rd party class, attach the required interfaces they honor, and have a functional extension of the system with very little work.
    • The system supports modules, which provide complex functionality through a single interface (as defined by the module). Classes provided by modules can only be used in the scope of that specific module, or extensions of that module.
    • The system supports extensions, which provide packages of additions to the system. Any classes provided in this manner will be registered universally throughout the system.
    • The system supports components, which are narrow-focus sets of universal classes. These classes are globally available, but only in the exact same scope as their definition, and cannot be accessed across scopes
    • The system supports adapters, which are fully-qualified extensions of Oroboros that provide interoperability with another package, 3rd-party library, or non-PHP programming construct. These must declare what sort of functionality they satisfy when registered with the Codex.
    • The system supports utilities, which are any non-core class that does not extend it's base, but does honor it's internal Api. Extending PHP base classes or simple 3rd-party libraries can be accomplished easily in this manner.
    • The system always makes it's api available. It is capable of parsing it's own documentation at runtime in the same manner as running Apigen, and returning this human-readable information upon request while developing or debugging.
    • Public Api's for all classes can be viewed at any time without any external documentation whatsoever, though external documentation is still provided.
    • All api interfaces MUST declare their dependencies [requires], any other api's they make available [provides], and any api's they satisfy [satisfies].
    • The system will never enforce opinion about how it's valid classes, extensions, modules, etc. are used outside of the scope of Oroboros core.

    This is only enforced internally. Your extension classes do not need to take this approach, but all core classes do. If you are writing extensions or modules for this architecture, these should be considered best practices. Otherwise this is just what you can expect from any classes within the package you choose to use.

    By following this approach, it should be possible to use a mixed approach system of your own making that uses a custom router, Zend libraries, Symphony models, and Laravel controllers for example, and still have the basic MVC order of operations remain intact without issue.

    The purpose of this approach is to make this library capable of easily wrapping any other structure, and being integrated quickly, without disrupting existing functionality.

    Other packages to extend Javascript and CSS functionality are also in development. This package will honor their presence, but will not enforce it.

    ####Object Generation####

    This system provides two means of streamlining object generation, a standard Factory, and a Prototyper.

    (A Builder approach may also be provided at a later date as another option)

    The factory works about as you'd expect, being assigned to generate either a specific class, scope of class, or type of class. If there is no corresponding factory via this approach, it cannot generate the class automatically. Factories honor extensions and overrides in their specific scope (determined by interface, and object registration).

    The other option is the Prototyper, which creates a single instance of a class, instantiates it, and then leaves it as a blank template for any further requests for that object. The prototyper provides a bit more robust index of classes it can generate than the Factory, allowing it to be bound to a specific scope rather than a specific class type. It is very good at creating tons of similar objects performantly, or preventing a class that requires many instances but has a heavy instantiation cost from affecting performance much. In order to use this, it should be kept in mind that the constructor will not fire again after the first time for any prototypical object. The clone magic method will fire for every subsequent object. To circumvent difficulty around this, Oroboros defines a standard function initialize, which any specific individual-object operations can be fulfilled in. A Factory will always call this method immediately after it obtains an instantiated object, so you do not need to call this in your constructor if you are using a Factory (although you can if you need to). If you are using a Prototyper without it being referenced by a Factory, you will need to manually call this function. If you wish to avoid this, you can call it directly at the end of both your constructor and clone magic methods, and it will always be satisfied before the object reaches you. Which approach you take should be a decision made by the specific developer based on need, and as such this distinction will never be decided for you in any out-of-scope sense.

    Both of these approaches work natively in their own scope, and either can be extended for custom use as needed without inherent reliance on the other.

    ###Provided Assets###

    This system provides the following assets out of the box:

    • Provides a Psr-3 compliant file-logger and null logger
    • Provides a standalone Psr-4 compliant autoloader
    • Provides a Psr-7 compliant stream and message library
    • Provides a dynamic router
    • Provides a dynamic validator, to simplify validation both internally and externally
    • Provides a REST client and server
    • Provides a robust flagging system, for designating operation environments or special conditions easily (eg: dryrun this database operation and tell me what would have happened instead of doing it, print debug results if this one is set, etc). All classes in the system inherit this functionality, and it can be added to your own via a trait without extending any Oroboros class whatsoever.
    • Provides a robust hook system, which allows shortcode-like markers to be placed in pretty much any context and replaced with expected values. This is done safely, so it never applies unless it is told to, invalid hooks are reduced to null results automatically (if nothing is registered to provide a replacement), replacements can be any scalar value or any valid callback, and a debug wrapper can be enabled to print where/why they were applied if unexpected values occur. Can be done recursively (in case hooks print more hooks), which can also be throttled by depth. This allows you for example to use a constant that isn't known until runtime in a json config file and still get the correct value explicitly, replace content in output retroactively, template from safe text files that don't execute code by being included, strip blacklisted info, etc).
    • Provides a streamlined database approach that works across RDBMS systems. No-SQL integration is also planned, using a consistent api for both (which also works for flat files or in-memory storage if you keep the keys somewhere coherent).
    • Provides all syntactically correct standard http response headers, and also most prevalent non-standard ones. There are both classes available to aggregate these, and interfaces that define them which can be individually used to make them available without class dependency as needed.
    • Provides several mature and performant regular expressions through an interface to be used wherever needed.
    • Provides enumerated reflectors of most Api interfaces that provide information without enforcing method structure (eg: request headers, response headers, regex, class types, class scopes, exception codes, http codes, etc). These can reverse parse a known value back into it's constant name, check a value, provide a full list within their scope, or be instantiated to provide a toString for the correct value. This enumeration can be easily extended to provide your own scoped sets of information, and accomplish numerous other handy validation or scope enforcement tasks easily, and can be used to always break operations that have invalid arguments supplied at runtime.
    • Provides an api for locking objects (read, write, execute) at runtime for additional security. They can be keyed to be unlocked by specific classes or by a specific key. This provides similar runtime object permissions as UNIX file permissions use.
    • Provides a fingerprinting system, which identifies individual object instances with a unique, non-colliding key, which allows it to be identified individually even in a set of thousands of identical objects as needed.
    • Provides customized exceptions that can be easily configured to be self reporting (off by default), and an error handling system that can deal with any uncaught ones. These all extend the standard SPL exception classes individually, and do not share a base class, so you can catch them easily by just catching the standard exception type, or handle them separately by catching first within their namespace, and then in the global namespace (if for example you had a chance of getting both a standard Oroboros exception and a standard normal \Exception in the same block of logic).
    • Provides a granular set of exception codes, granted by an interface that can be attached to anything to make them available.
    • Provides a cgi api that is inherently compatible with the http api, by abstracting cgi commands and responses using the same underlying Psr-7 message interface.
    • Provides both cgi and http request containers as a Psr-7 request message object with a consistent interface, and parameters which exist in only one environment or the other are provided as null values so they are non-colliding (so for example running a controller method that checks for cookies from cgi doesn't throw an error, it just gets a nulled result as if no cookies were provided, or accessing $argc from an http request gets an equivalent emulated value to what it would get if the request were cgi). This approach is used to prevent special conditionals usually needed to differentiate runtime environments or specialized classes to handle only one or the other (though you can still do either of these if you want to).
    • Provides an immediate snapshot of the current server environment (eg: if request is http or cgi, if ssl is enabled, if the request is ajax, etc), which can be made accessible to any class by implementing the EnvironmentApi interface (this inteface only provides constants, and requires no specific class structure). All of these values are also declared as global constants that can be accessed anywhere for one-off checks. All of these values will be [false] if they could not be determined, but are always present in both http and cgi scopes.
    • Provides abstraction for models, controllers, views, adapters, libraries, modules, components, extensions, utilities, templates, etc.
    • Provides several generic design patterns, implemented through traits or base classes (most support both), to allow for easy integration wherever needed.
    • Provides views that are inherently bound to a specific output type, will always set the correct headers for their content type automatically, and can exchange their content with other views to keep output type consistent.
    • Provides several different abstract models, which gives you flexibility as to how they are defined (active record style, Rest service record style, general data scope style, task-based style, generic, etc). This does not lock you in to any specific approach to business logic, which makes scalability significantly easier, and helps prevent coding yourself into a corner entirely.
    • Provides controllers that can be scoped specifically to web, cgi, ajax, etc, as well as unscoped controllers that can perform a set of related tasks across these scopes if adding additional controllers to handle other scopes is not worth the work. Since views are also interchangeable, this should maximize your capacity for code reuse without interfering with things that need a specific scope or allowing them to collide out-of-scope.
    • Provides a robust reporting system through the Codex, which can give you an accurate snapshot of everything happening at any given time. Custom debugging tools can leverage or extend this easily for very granular reporting.
    • Provides a Routine api, used for wrapping sets of procedural code in an object, so that it can be integrated into an object-oriented build easily (works great for bootstrapping standalone stuff, porting procedural code into an MVC system easily, or wrapping legacy code so it can be isolated in scope, and paired down into objects incrementally but still fulfill it's task).
    • Provides a Utility api, for creating helpers that don't wind up being a code smell.

    Each of these can be used as a standalone thing, in which case they do not enforce opinion. Using the entire system enforces only the opinion needed to keep it's order of operations and scope intact. Most of that structure only becomes burdensome if you try to do really stupid stuff that you will definitely regret later.

    (This feature set represents functionality that was already ported or is in the process of being ported. Additional functionality will be appended to this list as it becomes available)

    ###Planned Features###

    • Load balancing/cluster support (proxies, passthru's, load-balancing, object pooling, etc)
    • Security integration with the server through server adapters (Probably going to incur some slight apache/nginx/iis fiddling to integrate)
    • Bash/Bat cli interface
    • Microservice support
    • Subsystem wrapping support
    • Extension mapping support
    • ACL/Auth layer (Might wind up as an extension in another package, but the underlying integration has to be in this package to support it seamlessly)
    • More robust templating, and integration with common 3rd-party templating (Smarty, Twig, Blade, etc)

    These features will likely be broken off into extension packages wherever possible. They will be integrated directly into the core package if it is required that for system integrity.

    #Development Cycle#

    The release schedule of Oroboros core will follow the following guidelines:

    ##Test Driven Production##

    Features will not be released until their intended functionality can be proven via unit tests.

    This project will always provide it's test suite for verification, and the same test suite is integrated into the ci release pipeline. If all tests are not passed, it does not get tagged as a stable release.

    As of this writing, the dev branch should be considered extremely volatile. Do not track the dev branch until at least the 1.0.0 release, as functionality is expected to change there without notice for the time being.

    ##0.* Versions##

    These are to be considered alpha or beta builds, and should not be used for production software.

    These versions will support PHP 5.4.0 through the current 7.2 release.

    Much of this system is already written in prior deprecated builds, and will be rapidly ported into the existing project.

    Base level functionality is subject to change, but defined apis will not be altered unless absolutely neccessary.

    All minor releases will have subsequent unit tests that prove their functionality written and integrated into the release pipeline before being tagged as a valid version.

    Release functionality that does not have a unit test assigned to it should be considered incomplete. Currently active unit-test suites are visible in the phpunit.xml document.

    ###Testing Criteria for 0.* Releases

    • Test suite must not report any failed tests.
    • Test suite must also effectively test on all minor releases between 5.4.0 and 7.2.0.
    • Stable releases are tagged after verification of successful remote build via Bitbucket Pipelines.
    • Existing Api's may not be refactored after they are defined and successfully pass their test suite.
    • Backwards and forward compatibility through PHP 5.4.0 must be factored in if any tests fail before production release.
    • Any functionality that breaks backwards portability must be postponed to a subsequent release that has an announced minimum supported PHP version compatible with it's requirements (which means somewhere after the 1.0.0 release).
    • Existing functionality that interferes with the planned release schedule must be deprecated.
    • Deprecated functionality must be given replacement functionality that still fulfills it's purpose before deprecating it.
    • At least one minor version that contains both the deprecated functionality and the replacement functionality must be released before removal of the deprecated functionality occurs.
    • Issue tracking will be opened publicly when all existing functionality is ported into the library. Until that time, most bugs are unfinished features or incomplete ports.
    • After fully porting the old project, all subsequent bugs will be treated as normal bugs, and will be addressed through issue tracking as per normal.

    ##1.0.* Versions##

    Focus will no longer be given to maintaining compatibility with deprecated PHP versions.

    The initial stable 1.0.* releases will still support through PHP 5.4.0.

    From 1.1.* onward, additional functionality will not be written with backwards compatibility in mind beyond the currently released PHP versions with active support.

    The first time this occurs thereafter that breaks backwards compatibility, an immediate final release without that functionality will be pushed, and annotated as the last safe 5.4, 5.5, etc. release (however far back the intended functionality breaks).

    Thereafter, all releases that incidentally break backwards-compatibility will follow the same approach until the minimum currently supported version of PHP is reached.

    All subsequent 1.* releases will thereafter adhere to maintaining backwards compatibility as per the official PHP supported version list.

    ###Testing Criteria for 1.* Releases

    • Test suite must not report any failed tests.
    • Test suite must also effectively test on all minor releases between minimum supported version and 7.2.0 (or whatever is the highest current stable release at the time).
    • Stable releases are tagged after verification of successful remote build via Bitbucket Pipelines.
    • Existing Api's may only be refactored to remove functionality that is explicitly granted to support a PHP version that is no longer supported.
    • Backwards and forward compatibility through the current minimum PHP version defined by the project must be factored in if any tests fail before production release.
    • The current minimum PHP version supported MAY NOT exceed the minimum stable official supported PHP release.
    • Any functionality that breaks backwards portability beyond the current minimum supported PHP version must be postponed to a subsequent release that has an announced minimum supported PHP version compatible with it's requirements (which means somewhere after the 2.0.0 release most likely).
    • Existing functionality that interferes with the planned release schedule must be deprecated.
    • Deprecated functionality must be given replacement functionality that still fulfills it's purpose before deprecating it.
    • At least one minor version that contains both the deprecated functionality and the replacement functionality must be released before removal of the deprecated functionality occurs.
    • Issue tracking will be open publicly throughout the 1.0.0 release.
    • All bugs will always be treated as normal bugs, and will be addressed with priority over features through issue tracking as per normal.

    ##2.0.* Versions##

    When this project reaches it's 2.0.0 release, it will only maintain active compatibility with PHP 7.0+.

    The library will need to be very mature and stable already before that point is reached.

    The 2.0.0 release will center around refactoring existing code to leverage all of PHP 7+'s language constructs and additional functionality, to optimize existing Oroboros functionality. There should be little or no additional features in the initial 2.0.0 release.

    From 2.1.0+, the library will quickly add additional functionality that is not backward compatible, but will focus on not breaking any public api dependence on the initial 2.0.0 release (ie: your useage api will not change, but the PHP requirement for running it will elevate to 7.0 minimum).

    ###Testing Criteria for 2.* Releases

    • Test suite must not report any failed tests.
    • Test suite must also effectively test on all minor releases from 7.0.0 onward.
    • Stable releases are tagged after verification of successful remote build via Bitbucket Pipelines.
    • Existing Api's may only be refactored to remove functionality that is explicitly granted to support a PHP version that is no longer supported.
    • Backwards and forward compatibility through PHP 7.0.0+ must be factored in if any tests fail before production release.
    • The current minimum PHP version supported MAY NOT exceed the PHP 7.0.0 at any point unless one of those versions is officially deprecated by the PHP team, and can never exceed the minimum stable official supported PHP release.
    • Any functionality that breaks backwards portability beyond PHP 7.0.0, or the current minimum supported PHP version, whichever is higher must be postponed to a subsequent release that has an announced minimum supported PHP version compatible with it's requirements (which cannot be determined at this time).
    • Existing functionality that interferes with the planned release schedule must be deprecated.
    • Deprecated functionality must be given replacement functionality that still fulfills it's purpose before deprecating it.
    • At least one minor version that contains both the deprecated functionality and the replacement functionality must be released before removal of the deprecated functionality occurs.
    • Issue tracking will be open publicly throughout the 2.0.0 release.
    • All bugs will always be treated as normal bugs, and will be addressed with priority over features through issue tracking as per normal.

    #Docs

    Docs can be generated directly in source using apigen. Run apigen generate from the package root, and they will appear in docs/api, and can be viewed normally through your browser provided the routing allows it.

    Online documentation is pending. The system also has it's own runtime documentation engine through the Codex, which is not fully integrated into the current build, but should be shortly.

    ##System Requirements

    • PHP 5.4+
    • Composer

    ###PHP Supported Version

    Per current release:

    • 5.4.*+

    The app will whitescreen on 5.3 or lower due to the lack of traits, 5.3 and below will not be supported. 5.4 is compatible, but will patch in a couple emulated 5.5 functions that do not exist in 5.4. 5.5+ works natively.

    ##Installation

    With composer:

    composer require oroboros/core
    

    Note: Standalone builds are intended, but not currently feasible until the interal extension manager is completed. Until that time, composer will be a required dependency, but this requirement will be lifted at that time. Support for composer will continue thereafter, but not be a baseline requirement beyond this point.

    #Issues

    Still in very early development (Sort of. I wrote pretty much all of this already, but decided to refactor the build and have to port it all into the current architecture correctly. It was originally bundled as the guts of a CMS, and I decided that it would be more versatile as a standalone package, and the CMS should instead use it as a dependency).

    Issue tracking will be opened publicly after the core functionality is implemented.

    #Forks, Pull Requests, etc.

    Forks and pull requests will not be accepted until the full port from the older build is completed, as doing so will introduce underlying incompatibility with existing planned features.

    This restriction will be lifted when the system is fully ported, which will occur long before the 1.0.0 release.

    If you have suggestions, I'm more than happy to hear them, but I probably can't use your code as is right away until conversion is complete.

    Updated