SitePoint Sponsor

User Tag List

Results 1 to 10 of 10
  1. #1
    SitePoint Addict
    Join Date
    Jul 2014
    Posts
    236
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    [PHP] Creating a "portable" database connection / dependency injection container

    I see a lot of requests for help involving MySQL where database connections are created manually each time the DB needs accessed, whether it be inside a class or function, or just a "loose" file...also see a lot of require("connection.php")
    stuff as well.

    Thought I'd help out and show one method of creating a portable database connection, implementing PDO as the interface. Note, there are many ways to do this, but most of those ways involve doing crazy and/or questionable things (global variables, singletons, etc ).
    The method I'm about to introduce avoids any "code smell", and ensures that you can access your DB from any scope, anywhere, by simply setting the connection details one time, and importing it directly whenever needed.
    Shameless plug -- this is taken from a PHP framework that I am working on, located at https://github.com/arout/kwfusion

    Not only that, but at the end of this post, I will show you how to use this registry to make practically anything portable, not just your database. This registry application also acts as a dependency injector, so that you can ensure that every module you use has all of it's dependencies met before you use it!

    Before you get started, this will require that you have PHP version 5.3 or newer ( which is no problem, since you are using ver 5.5+ anyway, right? Right?? )

    Let's get to work:

    First, create a file called registry.php.

    Inside registry.php, insert the following code:

    PHP Code:
    class Application {

        protected static 
    $registry = array();
     
        
    /**
         * Add a new resolver to the registry array.
         * @param  string $name The id
         * @param  object $resolve Closure that creates instance
         * @return void
         */
        
    public static function register($nameClosure $resolve) {
            
            static::
    $registry[$name] = $resolve;
        }
     
        
    /**
         * Create the instance
         * @param  string $name The id
         * @return mixed
         */
        
    public static function run($name) {
            
            if ( static::
    registered($name) ) {
                
                
    $name = static::$registry[$name];
                return 
    $name();
            }
     
            throw new 
    Exception($name' not found');
        }
     
        
    /**
         * Determine whether the id is registered
         * @param  string $name The id
         * @return bool Whether to id exists or not
         */
        
    public static function registered($name) {
            
            return 
    array_key_exists($name, static::$registry);
        }

    The purpose of this file (registry.php) :

    As the file name suggests, this will act as a registry -- it will check if you have already registered a module (your database) and will throw an exception if you have not yet registered the requested model. You will only need to register a module one time.

    Next, in the same directory as registry.php, create a file named db.php. Include the following in db.php:

    PHP Code:
    <?php
    namespace Your\Namespace;
    use \
    PDO as PDO;

    ##########################################
    // Add `Database` to the registry array
    ##########################################

    if( ! class_exists("Application") ) require( dirname(__FILE__).'registry.php' );

    \
    Application::register'Database', function() {
        

    ## Enter your database connection settings here

    $host         "localhost";            // Most users should leave this set to localhost
    $sqlname  " ";                            // Name of database
    $sql_user  " ";                    // Username to connect to database
    $sql_pass " ";                        // Database password
            
    ## Setup PDO connection
    try {  
            
    $sql = new PDO("mysql:host=$host;dbname=$sqlname"$sql_user$sql_pass); 
            
    $sql->setAttributePDO::ATTR_EMULATE_PREPARESfalse ); 
            
    $sql->setAttributePDO::ATTR_ERRMODEPDO::ERRMODE_EXCEPTION );
    }  
    catch(
    PDOException $e) {  
            echo 
    '<h1>Fatal Error</h1>
            Could not establish connection to the database. '
    ;  
            
    file_put_contents('PDO_errors.txt'$e->getMessage(), FILE_APPEND);  
    }

        return 
    $sql;
    });
    Make sure to edit the namespace, as well as entering your DB settings.

    How to use:

    Importing the file
    Ideally, you already have Composer or some other method for autoloading files, in which case this will "just work" after you let your autoloader know where to find these files....otherwise, simply include the db.php file when you need to get to the database.

    Working with the database
    This is where this script earns it's bacon. Regardless of whether you are inside a class, function, other side of Jupiter, you need not worry about passing dependencies or anything else to get to your database. Simply call it like this:

    PHP Code:
    \Application::run('Database'); 
    More specifically, here is a working example of how to do it:

    PHP Code:
     // Store the connection as $db
     
    $db = \Application::run('Database');

     
    // Now just use it like any other PDO connection
     
    $query " SELECT something FROM some_table WHERE data = ? ";
     
    $result $db->prepare($q);
     
    $result->execute( array( $_POST['blah'] ) );

     foreach(
    $result as $row)
         echo 
    $row['blahblah']; 
    That's all there is to it.


    Adding module to the registry

    To add other modules to the registry, you simply:

    PHP Code:
    ###############################################
    // Add your custom router to the registry array
    ###############################################
    Application::register'Router', function() {
        
        return new 
    Your\Namespace\Router;
    }); 
    Give your module a name as the first argument ( in this case, I named my router....Router ).
    The line: return new Your\Namespace\Router; is simply instantiating the Router class that you would have created elsewhere.

    Then use it like you did the database:

    PHP Code:
     $router = \Application::run('Router'); 
    This in itself is not very useful if you already have autoloading set up....you can just as easily simply instantiate your class as normal. To recieve it's true value, we would use this as a dependency injection container.
    Here is an example of it injecting dependencies ( copy/pasted from my framework ):

    PHP Code:
    ###################################################
    // Add `System Controller` to the registry array //
    ###################################################
    Application::register'SystemController', function() {
        
        
    // Give the System Controller access to:
        // database, config, router, system model, system view
        
    $config    Application::run('Config');
        
    $route     Application::run('Router');
        
    $model     Application::run('SystemModel');
        
    $view     Application::run('SystemView');

        
    $sys_controller = new Fusion\System\SystemController$config$route$model$view );

        
    $sys_controller->dispatch();

        return 
    $sys_controller;
    }); 
    Feel free to ask any questions if you need help. Enjoy!
    Last edited by arout77; Jul 30, 2014 at 21:19. Reason: Formatting sucks

  2. #2
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,315
    Mentioned
    19 Post(s)
    Tagged
    1 Thread(s)
    You posted in Code Reviews, so I hope you don't mind some reviews.

    What I view as important issues:

    1. Don't reinvent the wheel.

    We should almost always use widely used and widely tested libraries rather than hand roll our own. Especially when we're writing a learning guide for others. We should teach people to use standard tools rather than the author's personal stuff. So rather than trying to introduce your hand-rolled container/registry, it may have been better to use a more widely adopted container, such as perhaps Pimple.

    2. You wrote a DI container, but didn't actually use DI. When you do something like this:

    Code PHP:
    $db = \Application::run('Database');

    That's a pattern called Service Locator, which is pretty much the antithesis of DI. I realize that at the end of your post, you started to do some DI, but this quoted part was advertised as "earning it's bacon", even though you almost certainly shouldn't be doing it this way.

    Medium issues

    1. Inside registry.php, you have class Application. It's a widely accepted convention that the file and the class should have the same name. So, for example, if you want to find the definition of class Application, then you should find it in the file Application.php. If it turns out that the class Application is in the file registry, then that's going to make it much harder to find things.

    2. You should avoid static properties wherever and whenever possible. In this case, I imagine it felt like you had to use static because you wanted to invoke in the service locator way:

    Code PHP:
    \Application::run('Database');

    But if you do it the DI way, then there would be just one instance of Application that you pass around. Or, even better, pass around instances of the services rather than the instance of the container.

    3. Your registry/container doesn't seem to have any notion of shared instances. So if three different spots in your application need to use the database, then your container will try to create three different instances. Ideally you want to pass around just one shared instance.

    4.
    catch(PDOException $e) {
    echo '<h1>Fatal Error</h1>
    Could not establish connection to the database. ';
    file_put_contents('PDO_errors.txt', $e->getMessage(), FILE_APPEND);
    }
    The database service shouldn't be echoing any HTML, nor should it be implementing its own ad hoc logger. Instead, for the HTML, the database service should allow the exception to bubble up, and the larger application will decide how to respond. And for the logging, there should be a separate logger service that gets injected into your database service.

    Minor issues

    1.
    if( ! class_exists("Application") ) require( dirname(__FILE__).'registry.php' );
    At this point, you should be using autoloading rather than manually requiring.
    "First make it work. Then make it better."

  3. #3
    SitePoint Addict
    Join Date
    Jul 2014
    Posts
    236
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Jeff Mott View Post
    You posted in Code Reviews, so I hope you don't mind some reviews.

    What I view as important issues:

    1. Don't reinvent the wheel.

    We should almost always use widely used and widely tested libraries rather than hand roll our own. Especially when we're writing a learning guide for others. We should teach people to use standard tools rather than the author's personal stuff. So rather than trying to introduce your hand-rolled container/registry, it may have been better to use a more widely adopted container, such as perhaps Pimple.

    2. You wrote a DI container, but didn't actually use DI. When you do something like this:

    Code PHP:
    $db = \Application::run('Database');

    That's a pattern called Service Locator, which is pretty much the antithesis of DI. I realize that at the end of your post, you started to do some DI, but this quoted part was advertised as "earning it's bacon", even though you almost certainly shouldn't be doing it this way.

    Medium issues

    1. Inside registry.php, you have class Application. It's a widely accepted convention that the file and the class should have the same name. So, for example, if you want to find the definition of class Application, then you should find it in the file Application.php. If it turns out that the class Application is in the file registry, then that's going to make it much harder to find things.

    2. You should avoid static properties wherever and whenever possible. In this case, I imagine it felt like you had to use static because you wanted to invoke in the service locator way:

    Code PHP:
    \Application::run('Database');

    But if you do it the DI way, then there would be just one instance of Application that you pass around. Or, even better, pass around instances of the services rather than the instance of the container.

    3. Your registry/container doesn't seem to have any notion of shared instances. So if three different spots in your application need to use the database, then your container will try to create three different instances. Ideally you want to pass around just one shared instance.

    4.

    The database service shouldn't be echoing any HTML, nor should it be implementing its own ad hoc logger. Instead, for the HTML, the database service should allow the exception to bubble up, and the larger application will decide how to respond. And for the logging, there should be a separate logger service that gets injected into your database service.

    Minor issues

    1.

    At this point, you should be using autoloading rather than manually requiring.


    Thanks Jeff,

    Great feedback!

    Actually, I originally posted this in the PHP thread; it got moved here for whatever reason, but feedback and discussion is always nice anyway.

    Regarding the DI / Service Locator; I definitely understand that it is used as a bit of both -- that may sound a little strange, but it's hard to give context without throwing a bunch of code from the framework here in order to give examples. I just gave the thread title DI since that is a term more people are probably familiar with. Honestly, the line between DI and service locator gets a bit blurry anyway, sometimes I use the terms interchangeably even though I know that is not correct.

    However, you have a great eye....indeed, this "registry" has no awareness of shared instances, which is the primary upgrade I wish to make when I come back to this and rewrite it. Originally just wanted a quick fix to get the framework rolling; but one thing led to another, before I knew it I have several thousand lines of code and still never got back to doing this properly!

    I wanted to scoff at the notion of avoiding static properties whenever possible -- I actually think this is one of those instances that static anything comes in handy, but this line here opened my eyes to what you were getting at:
    But if you do it the DI way, then there would be just one instance of Application that you pass around. Or, even better, pass around instances of the services rather than the instance of the container.
    I love this idea; if I recall correctly from browsing the docs a while back, that is exactly what Pimple does, isn't it?

    So you have definitely helped me approach the rewrite from a different -- and better -- angle, in my opinion...I was basically just going to build on top of this to add the ability to track shared instances, but your proposed approach is clearly a much better way, or at the very least much cleaner and easier, way. Thank you!

    Regarding your comments on the database, I agree that it is borderline shameful how it is managed now . This is another one of those "get it working now, improve it later things". And honestly, I haven't thought very far ahead on how I want to manage it, but that sounds like a good start when I get around to it....this framework is still in it's infancy, which is the exciting stage, but also overwhelming at times when you are eager to get everything done.

    But for the guys reading this response; don't get the wrong idea.....it's perfectly fine to use this as is for your project, especially if you are new(ish) or just want a quick fix, rather than setting up a DB every time. This is a poor design for a framework; a framework should provide professional level error handling and management....this does not do that (well, not in a professional way -- it just dumps an error to the screen and logs it to a text file).

  4. #4
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,315
    Mentioned
    19 Post(s)
    Tagged
    1 Thread(s)
    Some more food for thought.

    Let's say you start with a class Application, and the application "has a" container.

    Code PHP:
    <?php
     
    // src/kWFusion/Application.php
     
    namespace kWFusion;
     
    class Application
    {
        private $container;
     
        public function __construct()
        {
            $this->container = new \Pimple\Container();
        }
    }

    But your framework, like any framework, needs to provide some global services. Let's start with some basics like cache and logging.

    An uber simple cache class:

    Code PHP:
    <?php
     
    // src/kWFusion/Cache/ArrayCache.php
     
    namespace kWFusion\Cache;
     
    class ArrayCache
    {
        private $cache = [];
     
        public function get($key)
        {
            if (array_key_exists($key, $this->cache)) {
                return $this->cache[$key];
            } else {
                return null;
            }
        }
     
        public function set($key, $data)
        {
            $this->cache[$key] = $data;
        }
    }

    And an uber simple logger class:

    Code PHP:
    <?php
     
    // src/kWFusion/Log/FileLogger.php
     
    namespace kWFusion\Log;
     
    class FileLogger
    {
        private $filePath;
     
        public function __construct($filePath)
        {
            $this->filePath = $filePath;
        }
     
        public function log($message)
        {
            file_put_contents($this->filePath, $message, FILE_APPEND);
        }
    }

    First thing to notice is that both these classes are completely independent and self-contained. There's no dependency on any particular framework or on any particular container -- or any container for that matter. And as we move forward, we'd like to keep it that way. Also notice that the FileLogger class already contains a little bit of dependency injection... for the file path. It doesn't assume to know where the file should go; it doesn't assume to know about any globally available configuration; and it doesn't assume to know about any container.

    Now in our application, let's use these libraries to make services.

    Code:
    <?php
    
    // src/kWFusion/Application.php
    
    namespace kWFusion;
    
    class Application
    {
        private $container;
    
        public function __construct()
        {
            $this->container = new \Pimple\Container();
    
            $this->container['cache'] = function ($c) {
                return new \kWFusion\Cache\ArrayCache();
            };
    
            $this->container['logger'] = function ($c) {
                return new \kWFusion\Log\FileLogger('/var/logs/kWFusion.log');
            };
        }
    }
    But wait... what if some service needs to use another? After all, accessing a service such as the database "from anywhere" was one of the original goals of this thread. So what if, for example, the cache or the database needs to use the logger? Remember that we don't want the cache or any other service to know about the framework or the container. We want our classes to remain independent and self-contained. So how then does the cache get the logger?

    Dependency injection!

    Code:
    <?php
    
    // src/kWFusion/Cache/ArrayCache.php
    
    namespace kWFusion\Cache;
    
    class ArrayCache
    {
        private $cache = [];
        private $logger;
    
        public function __construct($logger)
        {
            $this->logger = $logger;
        }
    
        public function get($key)
        {
            if (array_key_exists($key, $this->cache)) {
                $this->logger->log('cache hit');
    
                return $this->cache[$key];
            } else {
                $this->logger->log('cache miss');
    
                return null;
            }
        }
    
        public function set($key, $data)
        {
            $this->cache[$key] = $data;
        }
    }
    The cache simply accepts an already-instantiated logger as a constructor argument. Maybe it was injected by a container, but maybe not. Maybe it was injected manually, but maybe not. Maybe it's being used from within your framework, but maybe not. This class doesn't assume anything, and therefore is flexible enough to fit most everything.

    In this case, of course, we choose to use it with your framework and a container.

    Code:
    <?php
    
    // src/kWFusion/Application.php
    
    namespace kWFusion;
    
    class Application
    {
        private $container;
    
        public function __construct()
        {
            $this->container = new \Pimple\Container();
    
            $this->container['cache'] = function ($c) {
                return new \kWFusion\Cache\ArrayCache($c['logger']);
            };
    
            $this->container['logger'] = function ($c) {
                return new \kWFusion\Log\FileLogger('/var/logs/kWFusion.log');
            };
        }
    }
    The framework is just the glue that connects these otherwise independent libraries together. You could continue in this way and make a database abstraction library that is usable and useful all by itself. Then in your framework, make a service that instantiates that database library. If the library needs a logger, use dependency injection so that the database library doesn't know or care whether you use a container or not. And then when some other library needs needs the database, again use dependency injection, and within your framework, use the container to perform that injection.
    "First make it work. Then make it better."

  5. #5
    SitePoint Addict
    Join Date
    Jul 2014
    Posts
    236
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Jeff Mott View Post
    Some more food for thought.

    Let's say you start with a class Application, and the application "has a" container.

    Code PHP:
    <?php
     
    // src/kWFusion/Application.php
     
    namespace kWFusion;
     
    class Application
    {
        private $container;
     
        public function __construct()
        {
            $this->container = new \Pimple\Container();
        }
    }

    But your framework, like any framework, needs to provide some global services. Let's start with some basics like cache and logging.

    An uber simple cache class:

    Code PHP:
    <?php
     
    // src/kWFusion/Cache/ArrayCache.php
     
    namespace kWFusion\Cache;
     
    class ArrayCache
    {
        private $cache = [];
     
        public function get($key)
        {
            if (array_key_exists($key, $this->cache)) {
                return $this->cache[$key];
            } else {
                return null;
            }
        }
     
        public function set($key, $data)
        {
            $this->cache[$key] = $data;
        }
    }

    And an uber simple logger class:

    Code PHP:
    <?php
     
    // src/kWFusion/Log/FileLogger.php
     
    namespace kWFusion\Log;
     
    class FileLogger
    {
        private $filePath;
     
        public function __construct($filePath)
        {
            $this->filePath = $filePath;
        }
     
        public function log($message)
        {
            file_put_contents($this->filePath, $message, FILE_APPEND);
        }
    }

    First thing to notice is that both these classes are completely independent and self-contained. There's no dependency on any particular framework or on any particular container -- or any container for that matter. And as we move forward, we'd like to keep it that way. Also notice that the FileLogger class already contains a little bit of dependency injection... for the file path. It doesn't assume to know where the file should go; it doesn't assume to know about any globally available configuration; and it doesn't assume to know about any container.

    Now in our application, let's use these libraries to make services.

    Code:
    <?php
    
    // src/kWFusion/Application.php
    
    namespace kWFusion;
    
    class Application
    {
        private $container;
    
        public function __construct()
        {
            $this->container = new \Pimple\Container();
    
            $this->container['cache'] = function ($c) {
                return new \kWFusion\Cache\ArrayCache();
            };
    
            $this->container['logger'] = function ($c) {
                return new \kWFusion\Log\FileLogger('/var/logs/kWFusion.log');
            };
        }
    }
    But wait... what if some service needs to use another? After all, accessing a service such as the database "from anywhere" was one of the original goals of this thread. So what if, for example, the cache or the database needs to use the logger? Remember that we don't want the cache or any other service to know about the framework or the container. We want our classes to remain independent and self-contained. So how then does the cache get the logger?

    Dependency injection!

    Code:
    <?php
    
    // src/kWFusion/Cache/ArrayCache.php
    
    namespace kWFusion\Cache;
    
    class ArrayCache
    {
        private $cache = [];
        private $logger;
    
        public function __construct($logger)
        {
            $this->logger = $logger;
        }
    
        public function get($key)
        {
            if (array_key_exists($key, $this->cache)) {
                $this->logger->log('cache hit');
    
                return $this->cache[$key];
            } else {
                $this->logger->log('cache miss');
    
                return null;
            }
        }
    
        public function set($key, $data)
        {
            $this->cache[$key] = $data;
        }
    }
    The cache simply accepts an already-instantiated logger as a constructor argument. Maybe it was injected by a container, but maybe not. Maybe it was injected manually, but maybe not. Maybe it's being used from within your framework, but maybe not. This class doesn't assume anything, and therefore is flexible enough to fit most everything.

    In this case, of course, we choose to use it with your framework and a container.

    Code:
    <?php
    
    // src/kWFusion/Application.php
    
    namespace kWFusion;
    
    class Application
    {
        private $container;
    
        public function __construct()
        {
            $this->container = new \Pimple\Container();
    
            $this->container['cache'] = function ($c) {
                return new \kWFusion\Cache\ArrayCache($c['logger']);
            };
    
            $this->container['logger'] = function ($c) {
                return new \kWFusion\Log\FileLogger('/var/logs/kWFusion.log');
            };
        }
    }
    The framework is just the glue that connects these otherwise independent libraries together. You could continue in this way and make a database abstraction library that is usable and useful all by itself. Then in your framework, make a service that instantiates that database library. If the library needs a logger, use dependency injection so that the database library doesn't know or care whether you use a container or not. And then when some other library needs needs the database, again use dependency injection, and within your framework, use the container to perform that injection.
    That's beautiful.

    I feel I should back track just a second. I absolutely understand that re-inventing the wheel is generally a bad idea, hence your recommendation of Pimple.
    However, I come from a C background, and wanted to get into web development as well, so I got hooked on PHP a while back -- the reason for creating my own DIC (in fact, the framework itself) is simply for the experience....I wouldn't be much of a developer if I wasn't a meddling tinkerer by nature

    So with that in mind, could I impose on you to look at this file below, and give me your opinion on how it fits into the grand scheme of things?

    This file is Init.php. It's purpose is to create instances of each class, and then pass it on to the registry:

    PHP Code:
    <?php
    /**
    * file: /vendor/Fusion/System/Init.php
    *
    * System initialization begins here
    *
    * We are simply using the registry (/vendor/Fusion/System/Registry.php)
    * to define dependency containers
    *
    */
    use \PDO as PDO;
    use \
    Memcache as Memcache;
    use \
    Memcached as Memcached;

    /**
     * Set default time zone
     */
    ini_set('date.timezone''America/New_York');

    /**
     * Filter input globally
     */
    ini_set('filter.default''full_special_chars');
    ini_set('filter.default_flags'0);

    ###############################################
    // Add `System Config` to the registry array //
    ###############################################
    Application::register('Config', function() {
        
        return new 
    Fusion\Config\Config;
    });

    ###############################################
    // Add `Memcache` to the registry array //
    ###############################################
    Application::register('Cache', function() {
        
        return new 
    Fusion\System\Cache;
    });

    ###############################################
    // Add `Opcache` to the registry array //
    ###############################################
    Application::register('Opcache', function() {
        
        return new 
    Fusion\System\Opcache;
    });

    ###############################################
    // Add `Loader` to the registry array //
    ###############################################
    Application::register('Loader', function() {
        
        return new 
    Fusion\System\Loader;
    });


    ###############################################
    // Add `System Routes` to the registry array //
    ###############################################
    Application::register('Router', function() {
        
        return new 
    Fusion\System\Router;
    });

    ###################################################
    // Add `System Controller` to the registry array //
    ###################################################
    Application::register('SystemController', function() {
        
        
    // Give the System Controller access to:
        // database, config, router, system model, system view
        
    $config Application::run('Config');
        
    $route     Application::run('Router');
        
    $model     Application::run('SystemModel');
        
    $view     Application::run('SystemView');

        
    $sys_controller = new Fusion\System\SystemController($config$route$model$view);

        
    $sys_controller->dispatch();

        return 
    $sys_controller;
    });

    ###################################################
    // Add `Unit Controller` to the registry array //
    ###################################################
    Application::register('UnitController', function() {

        return new 
    Fusion\System\UnitController();
    });
    ##############################################
    // Add `System Model` to the registry array //
    ##############################################
    Application::register('SystemModel', function() {

        
    $db Application::run('Database');
        return new 
    Fusion\System\SystemModel($db);
    });

    ##############################################
    // Add `System Views` to the registry array //
    ##############################################
    Application::register('SystemView', function() {
        
        
    $cache Application::run('Cache');
        return new 
    Fusion\System\SystemView($cache);
    });


    ##########################################
    // Add `Template` to the registry array //
    ##########################################
    Application::register('Template', function() {
        
        
    // Give the System Template access to:
        // config, router, system model, system view
        
    $config Application::run('Config');
        
    $route     Application::run('Router');
        
    $view     Application::run('SystemView');
        
    $load     Application::run('Loader');
        
    $data     = array();

        return new 
    Fusion\System\Template$config$route$view$load$data );
    });

    /**
     * Register Toolbox helpers prior to usage
     */

     // Build Pagination helper
    Toolbox::register("Pagination", function() {

        return new 
    Fusion\Toolbox\Pagination;
    });
     
    // Build Validation helper
    Toolbox::register("Validate", function() {

        return new 
    Fusion\Toolbox\Validate;
    });
     
    // Build Validation helper
    Toolbox::register("Input", function() {

        return new 
    Fusion\Toolbox\Input;
    });
     
    // Build Validation helper
    Toolbox::register("Sanitize", function() {

        return new 
    Fusion\Toolbox\Sanitize;
    });
     
    // Build Hash helper
    Toolbox::register("Hash", function() {
        
        return new 
    Fusion\Toolbox\Hash;
    });
     
    // Build Hash helper
    Toolbox::register("Formatter", function() {
        
        return new 
    Fusion\Toolbox\Formatter;
    });
     
    // Build Hash helper
    Toolbox::register("Breadcrumbs", function() {
        
        return new 
    Fusion\Toolbox\Breadcrumbs;
    });
     
    // Build Hash helper
    Toolbox::register("Environment", function() {
        
        return new 
    Fusion\Toolbox\Environment;
    });


    So to give a little context of what is happening....

    It's an MVC framework, so we're using index.php as the front controller.

    The front controller sets some basic settings, defines a few file paths, and fetches Registry.php and Init.php. Composer takes care of loading the classes for init.php.

    What happens is, first, Registry.php is loaded, so that the "factory" class (the class named Application) is ready to accept parameters.
    Init.php is then loaded, and is a pre-defined set of containers passed on to the registry....that's a bummer, ideally, I would like to have the containers create themselves rather than resorting to what you see in Init.php.

    As you can see above, this is how the entire system is initialized and services are passed around.

    Now, just one more thing to keep in mind...I do intend to release this at some point for public use; and the biggest goals for this is to be perfomant, and more importantly, extremely easy to use.
    For example, you are probably wondering what the heck that bit about Toolbox::register is doing...
    I have a second registry, (the class name is Toolbox, rather than Application) that functions exactly the same. The toolbox is just some utililties that take care of the usual stuff -- pagination, validation, etc -- and is passed to the base system controller, and through inheritance all the controllers that are spawned by the user have access to the toolbox ( by using $this->toolbox('Validate')->form(); or $this->toolbox('Encrypt')->input(); etc... )
    Basically, I'm going to great lengths to make sure the framework has as simple, consistent and 'natural' syntax / function calls as possible.

    Do you see anything here in this flow that raises any red flags? Or could just use some refinement? I think it looks solid, but I'm also really close to the proverbial forest, so I may not be seeing the trees. The one thing I do see that needs done is to be able to create these containers dynamically, rather than hard coding them in.

    Thanks for the time!

  6. #6
    SitePoint Wizard bronze trophy Jeff Mott's Avatar
    Join Date
    Jul 2009
    Posts
    1,315
    Mentioned
    19 Post(s)
    Tagged
    1 Thread(s)
    Broadly speaking, I'd say that's fine-ish.

    I would avoid changing ini settings. The problem is that these settings aren't local to your framework. They're global and affect everything while that script is running. Some users of your framework may wonder why their PHP environment isn't behaving as they had configured it.

    I'd also avoid setting filter full_special_chars. That's like going back to magic quotes behavior, but for HTML rather than SQL.

    The rest for a while seems fine. You're defining services.

    The toolbox part seems unnecessary. Those services could still be defined as part of your main container, just like every other service.

    The ideal, from a software architecture perspective, is for every service to be injected. Something like:

    Code PHP:
    class SomeController
    {
        private $validator;
        private $encryptor;
     
        public function __construct($validator, $encryptor)
        {
            $this->validator = $validator;
            $this->encryptor = $encryptor;
        }
     
        public function someAction()
        {
            // ...
            $this->validator->form();
            // ...
        }
    }

    But I also understand the desire to make it easier to use. I know of other frameworks that allow the option to sacrifice architectural "purity" in favor of convenience in exactly the sort of way you want. So my only comment is that you don't need a separate container (toolbox) in order to achieve that.

    Code PHP:
    class SomeController extends YourBaseController
    {    
        public function someAction()
        {
            // ...
            $this->getService('validator')->form();
            // ...
        }
    }
    "First make it work. Then make it better."

  7. #7
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,194
    Mentioned
    17 Post(s)
    Tagged
    5 Thread(s)
    Quote Originally Posted by arout77
    I feel I should back track just a second. I absolutely understand that re-inventing the wheel is generally a bad idea, hence your recommendation of Pimple.
    However, I come from a C background, and wanted to get into web development as well, so I got hooked on PHP a while back -- the reason for creating my own DIC (in fact, the framework itself) is simply for the experience....I wouldn't be much of a developer if I wasn't a meddling tinkerer by natur
    In my opinion you should be "meddling tinkerering" with something actually useful like Symfony 2 or Laravel not reinventing the wheel. I mean from you have posted it doesn't look like you have that great of an understanding of all the things that encompass such frameworks which you are trying to "reinvent". Not to be harsh but I call it like I see it and it would probably do you a world of more good getting mre familiar with a tried and true solution by the masters of the PHP world rather than lolly gagging with your framework. That isn't to say there is room for innovation but innovation to be part of actual real-world problem that lacks a well done solution in the open source realm.
    The only code I hate more than my own is everyone else's.

  8. #8
    SitePoint Addict
    Join Date
    Jul 2014
    Posts
    236
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    In my opinion you should be "meddling tinkerering" with something actually useful like Symfony 2 or Laravel not reinventing the wheel. I mean from you have posted it doesn't look like you have that great of an understanding of all the things that encompass such frameworks which you are trying to "reinvent". Not to be harsh but I call it like I see it and it would probably do you a world of more good getting mre familiar with a tried and true solution by the masters of the PHP world rather than lolly gagging with your framework. That isn't to say there is room for innovation but innovation to be part of actual real-world problem that lacks a well done solution in the open source realm.
    Harsh is good, it prevents confusion, so I appreciate the response.

    As I said, this is for the experience, not an attempt to recreate the wheel. I could certainly benefit from taking ideas from those frameworks, but becoming proficient in a particular framework doesn't really teach you very much. Recreating what they have already done is invaluable as a learning tool. In other words, it would take me 10 minutes to read Pimple's docs and learn how to use it's DIC.....but that wouldn't teach me a lick about what it takes to create one, or why one is even necessary.

    Ironically, I do know the Codeigniter and Yii frameworks pretty well...and to emphasize the above point, all it taught me was how to use it's premade tools. I don't want to drive the car, I want to build it!

  9. #9
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,194
    Mentioned
    17 Post(s)
    Tagged
    5 Thread(s)
    Quote Originally Posted by arout77
    Now, just one more thing to keep in mind...I do intend to release this at some point for public use; and the biggest goals for this is to be perfomant, and more importantly, extremely easy to use.
    I don't understand this mentality from everyone who seems to want to write their own framework that it is going to be "simple". Simple is arbitrary based ones own experiences and knowledge of the systems at hand. What is simple to one person is complex to another. Not to mention any useful project will naturally evolve over time with new features and as we all know features naturally effect complexity. I believe it is more important to focus on features and providing thorough documentation than have some arbitrary goal of simplicity. I would go as far to say simplicity is many times a cope out for not providing useful features and/or understanding/unwillingness to embrace tried and true architecture/patterns due to pure stubbornness.
    The only code I hate more than my own is everyone else's.

  10. #10
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,194
    Mentioned
    17 Post(s)
    Tagged
    5 Thread(s)
    Quote Originally Posted by oddz
    As I said, this is for the experience, not an attempt to recreate the wheel. I could certainly benefit from taking ideas from those frameworks, but becoming proficient in a particular framework doesn't really teach you very much. Recreating what they have already done is invaluable as a learning tool. In other words, it would take me 10 minutes to read Pimple's docs and learn how to use it's DIC.....but that wouldn't teach me a lick about what it takes to create one, or why one is even necessary.
    I will have to completely disagree there. Software exists to solve real world business problems. I would say there is much more value in learning tools that already exist that partially or completely (unlikely) solve those problems than reinventing them. Naturally one will become familiar with all in and outs of such tools over time. I've never had a CMS or Framework meet all requirements of a project. So naturally those tools must be pushed passed their limits and more often that not that requires not just learning the API but digging into the source code itself. I would definitely say there is more value in being introduced to a variety of different programming styles and developers than sitting on an island of ones own code.
    The only code I hate more than my own is everyone else's.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •