Jump to content

Invision Community Blog


Managing successful online communities

Mark
Sign in to follow this  
 

4.0 - Dev Introduction

A while back, we casually mentioned in a blog entry that 4.0 would be next major version after 3.4. Development of 4.0 is underway and we're going to be using this new blog to talk about development as we go.

As Brandon mentioned a couple of days ago - the format of these entries is going to be developer-specific. If what we're saying doesn't make much sense right now, we will still be putting announcements up in our main blog when they're finished and ready for everyone to see.

Because of that, it's also worth bearing in mind that everything is subject to change. I'm going to be posting code samples, screenshots and so forth - but everything in this blog is a work in progress - not the final product - and that will probably show.

With that out the way - let's talk about 4.0! :D



The file structure

Currently, applications are mostly self contained in their folders (which is either /admin/applications, /admin/applications_addon/ips or /admin/applications_addon/other) however, other files are dotted around in /interface, /public, etc. In 4.0, applications will be completely self-contained within a single /applications directory.

An application directory will look something like this:

  • extensions
  • dev
    • css
    • html
    • img
    • js
    • lang


      • admin
      • front (it's "front" rather than "public" now)


    [*]interface [*]modules[*]setup [*]sources [*]tasks [*]xml
    You will notice the inclusion of a /dev folder. This will not actually be shipped in production, but rather replaces /cache/lang_cache/master_lang and so forth in the 3.x line.

    Outside of the applications directory, there will be a "system" directory, which contains core framework classes.


    Namespaces and autoloading

    In 4.0, we'll be making use of PHP namespaces and using an autoloader. The best way to demonstrate how this works is with a few examples:
    • classDb (/ips_kernel/classDb.php) is now IPSDb and located in /system/Db/Db.php
    • output (/admin/sources/classes/output/publicOutput.php) is now IPSOutput and located in /system/Output/Output.php
    • class_forums (/admin/applications/forums/sources/classes/forums/class_forums.php) is now IPSforumsForum and located in /applications/forums/sources/Forum/Forum.php
    • IPSDispatcherFront and IPSDispatcherAdmin are two new classes (with similar functionality to ipsController in 3.x) and both extend IPSDispatcherDispatcher - all 3 are located in /system/Dispatcher/ in individual files.


    Better framework design

    Where appropriate, classes are being refactored to make better use of appropriate design patterns. One lovely side-effect of this is ipsRegistry no longer exists.

    Instead of, for example ipsRegistry::DB() you now use IPSDb::i() - the Db class uses a multiton design pattern (I didn't pass any arguments in that example, which doing will cause the Db class to load conf_global.php and create the default database connection, but I could have passed it a key) - the i method in this case will create the database connection if it doesn't already exist.

    To give another example - IPSMember (the new IPSMember) uses an Active Record pattern. So there's no more of this:
    IPSMember::isInGroup( 1, 4 );
    It's now, the much more logical:
    IPSMember::load( 1 )->isInGroup( 4 );


    Monkey patching hooks

    One of the great things about the IPS Community Suite is hooks - you can easily create a class and instruct the framework to use that instead of a core class.

    Now, I don't know about you, but I really, really, really hate having to do this:
    $class = IPSLib::loadLibrary( '/path/to/file', 'myClass' );
    $object = new $class;

    Especially if what you want to call is a static method, in which case it can't be done. You want to of course, just be able to do:
    $object = new myClass;
    or:
    myClass::myStaticMethod();

    There is a concept in software engineering to do this sort of thing, called monkey patching, and by clever use of the autoloader, we've managed to make this work. loadLibary and loadActionOverloader are no more.




    These points are of course, just the beginning of 4.0. Stay tuned for more :smile:
    Sign in to follow this  

    Comments



    Recommended Comments

    Where will cached css, template, language and error files be stored? Will multi-server environments and a git / deploy -system be better supported?

     

    Improved ability to define these cache paths in constants.php / initdata? 

     

    The file structure is something I'm really interested in. You should avoid mixing generated files and static files like you do today. 

    Share this comment


    Link to comment
    Share on other sites

    I can't say I'm sold on this namespace approach, it seems to lend itself to messy dense syntax more than anything... but I guess I should wait for actual code samples before judging it too harshly.

     

    What logic does the autoloader work by? IPSDb maps to system/Db/Db.php, so it's not a direct path lookup. How is the patching actually implemented (or have you not gotten that far yet)?

    Share this comment


    Link to comment
    Share on other sites

    Great to get some insights about the new structure. But instead of introducing multitons wouldn't it be more logical to use a dependency injection container and make it available anywhere? It would enhance testability of the code and use cases like this are also well supported. App developers would also benefit from a DIC as they could easily specify which services they need and get a perfectly configured instance from the container instead of wiring all the needed dependencies themselves.
     

    What logic does the autoloader work by? IPSDb maps to system/Db/Db.php, so it's not a direct path lookup.

    There are various lookup ways possible so it depends on the concrete implementation. You can also have a static class map with classes anywhere which could be then registered with the autoloader. Here is an example for PSR-0 compliance: https://github.com/composer/composer/blob/master/src/Composer/Autoload/ClassLoader.php - there are also implementations for class maps and even an generator for autoloading.

    Share this comment


    Link to comment
    Share on other sites

    Could I get some clarification on the directory structure?  Will the source code that makes up an application (everything under /admin/applications_addon/other/*** today) still be under a directory that is not ever intended to be accessible to someone fiddling with the URL?  What about the stuff an application provides that has to be publicly accessible, like javascript files, CSS, and images?  Obviously the browser needs to be able to load those, so the URL to them needs to be publicly available, but I would guess it still needs to be separated from the other application files like it is currently done today.

     

    I guess my issue is, you make it seem like an application is going to put everything under one directory in 4.0, but I'm not sure how that can be accomplished while allowing public stuff to be public and protecting the non-public stuff.

    Share this comment


    Link to comment
    Share on other sites

    I guess my issue is, you make it seem like an application is going to put everything under one directory in 4.0, but I'm not sure how that can be accomplished while allowing public stuff to be public and protecting the non-public stuff.

    My guess is the Suite delivering the content, similar to how it transfers files.

    Share this comment


    Link to comment
    Share on other sites

    Where will cached css, template, language and error files be stored? Will multi-server environments and a git / deploy -system be better supported?

     

    Improved ability to define these cache paths in constants.php / initdata? 

     

    The file structure is something I'm really interested in. You should avoid mixing generated files and static files like you do today. 

     

    Yes, all generated files will be in a single folder :)

     

     

    What logic does the autoloader work by? IPSDb maps to system/Db/Db.php, so it's not a direct path lookup. How is the patching actually implemented (or have you not gotten that far yet)?

     

    If the namespace matches a folder rather than a file, it'll look for a file with the same name as the folder.

     

     

     

    So we would literally use something like IPSDb::buildAndFetch( array() ); for example?

     

    If we wanted to, could we do something like $db = IPSDb?

     

    Yes, buildAndFetch still exists and uses the same syntax (well, the syntax has had some added features - keep an eye out for my next blog entry, but it'll be backwards compatible).

     

     

    If you wanted a shortcut you'd do:

    $db = IPSDb::i();
    

     

     

     

     

    But instead of introducing multitons wouldn't it be more logical to use a dependency injection container and make it available anywhere?

     

    Perhaps in some cases, but the database class has no need for it.

     

     

     

    Could I get some clarification on the directory structure?  Will the source code that makes up an application (everything under /admin/applications_addon/other/*** today) still be under a directory that is not ever intended to be accessible to someone fiddling with the URL?  What about the stuff an application provides that has to be publicly accessible, like javascript files, CSS, and images?  Obviously the browser needs to be able to load those, so the URL to them needs to be publicly available, but I would guess it still needs to be separated from the other application files like it is currently done today.

     

    I guess my issue is, you make it seem like an application is going to put everything under one directory in 4.0, but I'm not sure how that can be accomplished while allowing public stuff to be public and protecting the non-public stuff.

     

    Applications will be in an /applications directory - the application folder, when built, will only contain PHP files that won't do anything when accessed so it being publicly accessible is not an issue.

     

    When you write javascript, CSS and put images into your app, they'll go into /applications/<app>/dev/ - then when you build your application, they'll be "compiled" much like how your skin templates are in 3.x. The /dev directory is then removed in the build.

    Share this comment


    Link to comment
    Share on other sites

    Yes, buildAndFetch still exists and uses the same syntax (well, the syntax has had some added features - keep an eye out for my next blog entry, but it'll be backwards compatible).

     

     

    If you wanted a shortcut you'd do:

    $db = IPSDb::i();
    

    'i' being short for 'instance', so to speak?

     

     

    Applications will be in an /applications directory - the application folder, when built, will only contain PHP files that won't do anything when accessed so it being publicly accessible is not an issue.

     

    When you write javascript, CSS and put images into your app, they'll go into /applications/<app>/dev/ - then when you build your application, they'll be "compiled" much like how your skin templates are in 3.x. The /dev directory is then removed in the build.

    I think his question comes down to, where would things like js/css files be loaded from?

    Share this comment


    Link to comment
    Share on other sites

    'i' being short for 'instance', so to speak?

     

    That's correct.

     

    I think his question comes down to, where would things like js/css files be loaded from?

     

    In developer mode, /applications/<app>/dev/js/ and /applications/<app>/dev/css/.

    In normal mode, something to the effect of /cache/js/ and /cache/css/<skinid>/

    Share this comment


    Link to comment
    Share on other sites

    Is there such a thing as to many hooks? If so would it be a good idea to show on the backend on long each hook takes to load it data or some type of statics that allows a Admin know what is causeing a problem then just an error message. If that could be done it could show what is the issue quicker and allow the problem to be rooted out and fix.

    Share this comment


    Link to comment
    Share on other sites

    Is there such a thing as to many hooks? If so would it be a good idea to show on the backend on long each hook takes to load it data or some type of statics that allows a Admin know what is causeing a problem then just an error message. If that could be done it could show what is the issue quicker and allow the problem to be rooted out and fix.

     

    We will be working on more robust error capturing for hooks to allow sites to function even if a hook isn't working 100%.  We have not settled on all of the specifics just yet, however.

    Share this comment


    Link to comment
    Share on other sites

    When you write javascript, CSS and put images into your app, they'll go into /applications/<app>/dev/ - then when you build your application, they'll be "compiled" much like how your skin templates are in 3.x.

    The /dev directory is then removed in the build.

    Please No? I would rather not distribute the folder(remove it myself) than have it remove the directory building out.... that would get annoying rather quickly for any dev that does several builds for any given release.. Please don't add more time consumption for those of us actually test and re-test our work... hooks in 3.x renaming has been a joy, more like such I'm leery of.

    Share this comment


    Link to comment
    Share on other sites

    Please No? I would rather not distribute the folder(remove it myself) than have it remove the directory building out.... that would get annoying rather quickly for any dev that does several builds for any given release..


    Well yeah, it won't delete itself.

    Share this comment


    Link to comment
    Share on other sites

    To give another example - IPSMember (the new IPSMember) uses an Active Record pattern. So there's no more of this:

    IPSMember::isInGroup( 1, 4 );
    

    It's now, the much more logical:

    IPSMember::load( 1 )->isInGroup( 4 );
    

    .... we *will* still be able to load multiple records with one call.... right?

    $members = IPSMember::load( $mids );
    

    Share this comment


    Link to comment
    Share on other sites

    In developer mode, /applications/<app>/dev/js/ and /applications/<app>/dev/css/.

    In normal mode, something to the effect of /cache/js/ and /cache/css/<skinid>/

    Don't support something like /cache/js/applications/<app>/ and such could be used?  Anywhere where it would be very easy to remove just a few mere folders and suddenly it's completely removed from the server (database being a separate thing of course).

    Share this comment


    Link to comment
    Share on other sites

    .... we *will* still be able to load multiple records with one call.... right?

    $members = IPSMember::load( $mids );
    

     

    That example would (probably) be contrary to the active record pattern--each instance corresponds to one row. If they're smart (they seem to be), they'll most likely implement something like collections for loading and handling groups of records consistently.

    Share this comment


    Link to comment
    Share on other sites

    I assume IPS is an alias... taking the instance of working with 2 or more IP.Suites, is that alias going to be of conflict, or is it possible to run more than one suite in the code via use ... as statements?

    use IPS as IPSOne;
    use forumsIPS as IPSTwo;
    

    Share this comment


    Link to comment
    Share on other sites

    Perhaps in some cases, but the database class has no need for it.

    Why would the database class be different than any other class? What if I want to test my code which depends on the database and I have no way of mocking it to have testing in isolation? Having it as a injectable dependency makes mocking it in tests easy.
    I just want to state that this major version bump is the best opportunity to establish well tested and proven design principles which lead to better and testable code.

    Share this comment


    Link to comment
    Share on other sites

    .... we *will* still be able to load multiple records with one call.... right?

    $members = IPSMember::load( $mids );
    

     

     

    Yes, but the syntax will be a little different.

     

     

    Don't support something like /cache/js/applications/<app>/ and such could be used?  Anywhere where it would be very easy to remove just a few mere folders and suddenly it's completely removed from the server (database being a separate thing of course).

     

     

    The /cache folder will be a true cache, you can delete the entire folder if you like and it'll rebuild itself with what is needed.

     

     

    I assume IPS is an alias... taking the instance of working with 2 or more IP.Suites, is that alias going to be of conflict, or is it possible to run more than one suite in the code via use ... as statements?

    use IPS as IPSOne;
    use forumsIPS as IPSTwo;
    

     

    IPS is the actual namespace name.

     

    If you were working with more than one installation, you'd just use different DB connections - after all, their source files will be the same.

     

     

     

    IPSDb::i( 1 )->...
    IPSDb::i( 2 )->...
    

     

     

     

     

    Why would the database class be different than any other class? What if I want to test my code which depends on the database and I have no way of mocking it to have testing in isolation? Having it as a injectable dependency makes mocking it in tests easy.
    I just want to state that this major version bump is the best opportunity to establish well tested and proven design principles which lead to better and testable code.

     

     
    I agree with your notion, but not the solution. In normal operation, the database connection doesn't change (you might have more than one, hence a multiton rather than a singleton, but the notion of "I want this class to run queries against a MySQL Sever installation" is constant). There's no point in writing factory methods for injecting dependancies for every single class (which would be required, as every class has different and multiple dependancies) if those dependancies never change.
     
    In testing of course, you may want to mock it so you're testing your code in isolation - but there are other solutions for doing this which don't entail completely role-switching how an object accesses the class. One example, which was mentioned in this blog entry, is monkey patching.

    Share this comment


    Link to comment
    Share on other sites

    The /cache folder will be a true cache, you can delete the entire folder if you like and it'll rebuild itself with what is needed.

    I misworded it and in fact kind of confused myself with what you quoted.  I meant, with the apps, everything being contained within folders where you could delete a few folders and only be deleting content/files for that one app.

     

    For example, as it is now, there are js files for Gallery that go into the public/js folder, with the rest of the js files.  So, but intended design, being able to delete the following folders (for example) to completely remove the gallery app:

    /admin/applications/gallery/*

    /public/js/applications/gallery/*

    /public/css/applications/gallery/*

     

    Although the cache folder being a true cache is nice to hear.  Is the rebuild expected to hurt performance a lot or should that be a minor issue?  For example, let's say someone creates a task to delete everything inside of the cache folder once a week, so that everything in there is fresh.  Would that be a bad idea because of how often it would be or would that be just fine?  When doing backups, obviously that folder would be able to get ignored which could help reduce the size of a backup file.

     

     

    IPS is the actual namespace name.

     

    If you were working with more than one installation, you'd just use different DB connections - after all, their source files will be the same.

    IPSDb::i( 1 )->...
    IPSDb::i( 2 )->...
    

    Is there an AIM to make it so that multiple domains could call on the same files but generate different content?

    Share this comment


    Link to comment
    Share on other sites

    IPS is the actual namespace name.

     

    If you were working with more than one installation, you'd just use different DB connections - after all, their source files will be the same.

    IPSDb::i( 1 )->...
    IPSDb::i( 2 )->...
    

    avoiding direct database calls where the framework handles it is what I am talking about....

    IPSMember::load( 1 )->isInGroup( 4 );
    

    is(?) going to differ if each one is using the default database connection.... no? Confused.

    Share this comment


    Link to comment
    Share on other sites

    Join the conversation

    You can post now and register later. If you have an account, sign in now to post with your account.
    Note: Your post will require moderator approval before it will be visible.

    Guest
    Add a comment...

    ×   Pasted as rich text.   Paste as plain text instead

      Only 75 emoji are allowed.

    ×   Your link has been automatically embedded.   Display as a link instead

    ×   Your previous content has been restored.   Clear editor

    ×   You cannot paste images directly. Upload or insert images from URL.

    Loading...