zendframework / zend-expressive-skeleton

Begin developing PSR-7 middleware applications in seconds!
BSD 3-Clause "New" or "Revised" License
136 stars 90 forks source link

[Suggestions] Enable zfcampus/zf-development-mode like in ZendSkeletonApp #104

Closed gabbydgab closed 7 years ago

gabbydgab commented 8 years ago

Looking into zf-development-mode configuration it only needs a distribution file with an array and is initially executed during composer create-project command along with this is to enable Whoops Error Handling.

For reference, kindly check my ZfExpressiveModularApp implementation

Here is the proposed development.config.php.dist file content:

use Zend\Expressive\Container;

return [
    'debug' => true,
    'config_cache_enabled' => false,

    // Whoops error template handler
    'dependencies' => [
        'invokables'    => [
            'Zend\Expressive\Whoops'            => Whoops\Run::class,
            'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class,
        ],
        'factories'     => [
            'Zend\Expressive\FinalHandler' => Container\WhoopsErrorHandlerFactory::class
        ]
    ],
    'whoops' => [
        'json_exceptions' => [
            'display'    => true,
            'show_trace' => true,
            'ajax_only'  => true,
        ]
    ]
];

The following configuration is allowing the default

It needs to update also the config.php file with the following:

} else {
    // Load configuration from autoload path
    foreach (Glob::glob('config/autoload/{{,*.}global,{,*.}local}.php', Glob::GLOB_BRACE) as $file) {
        $config = ArrayUtils::merge($config, include $file);
    }

    // Development mode enabled
    if (file_exists(__DIR__ . '/../config/development.config.php')) {
        $config = ArrayUtils::merge($config, require __DIR__ . '/../config/development.config.php');
    }

    // Cache config if enabled
    if (isset($config['config_cache_enabled']) && $config['config_cache_enabled'] === true) {
        file_put_contents($cachedConfigFile, '<?php return ' . var_export($config, true) . ';');
    }
}

Lastly, for the composer.json file:


"require-dev": {
        "zfcampus/zf-development-mode": "^3.0",       
        "filp/whoops": "^2.0"
    },
    "scripts": {
        "development-disable": "zf-development-mode disable",
        "development-enable": "zf-development-mode enable",
        "development-status": "zf-development-mode status",
        "post-create-project-cmd": ["@development-enable"]
    }

Any thoughts?

geerteltink commented 8 years ago

I like the idea. If this would be implemented, the config/development.config.php file must be included in .gitignore so it won't end up on the production server. I see in your repo that you did that already but I'm still mentioning it here in case it's going to be added.

The reason I would be against this is that (as far as I can tell) the zf-development-mode command doesn't remove the possible cached configuration... yet. Also if this requires renaming src to module then again I would advise against this until there is a decision made on what structure to use (zendframework/zend-expressive#176).

I'm not sure how and if this would affect module support (#54).

gabbydgab commented 8 years ago

@xtreamwayz no changes for the directory structure for now, if this will be added. The changes will be on the files mentioned above. So, it still complies the current skeleton app.

With regards to the modular approach, can be discussed in a different thread, it still uses the "mtymek/expressive-config-manager" package but I would suggest to create a separate repository for the core (common) expressive configuration (as Zend\Expressive\ConfigProvider) and add a specific framework-based configuration such as Zend components.

Sample config/modules.config.php

use Zend\Expressive\ConfigManager\ConfigManager;
/**
 * Use Fully Qualified Namespace to enable the expressive configuration
 */
$modules = [
    Zend\Expressive\ConfigProvider::class, // Minimal Components
    Zend\Component\ConfigProvider::class, // Zend Components
    App\ConfigProvider::class, //Application Settings or can be Album tutorial https://github.com/zendframework/zend-expressive/issues/176
];
return (new ConfigManager($modules))->getMergedConfig();

With this approach, we are eliminating the mis-configuration of the expressive skeleton and set this as a library (composer type)

composer.json file (assuming we loaded the zend-component-installer in the skeleton composer.json)

"require": {
        "php": "^7.0",
        "roave/security-advisories": "dev-master",
        "zendframework/zend-expressive": "^1.0",
        "zendframework/zend-expressive-helpers": "^2.0"
},
"autoload": {
        "psr-4": {
             "Zend\\Expressive\\": "src/"
        }
},
"extra": {
       "zf": {
            "config-provider": "Zend\\Expressive\\ConfigProvider"
        }
},
"autoload-dev": {
       "psr-4": {
            "ZendTest\\Expressive\\": "test/"
        }
}

The only file in the repo. (src/ConfigProvider.php)

namespace Zend\Expressive;
final class ConfigProvider
{
    public function __invoke()
    {
        return [
            "dependencies"          => $this->getServiceConfig(),
            "middleware_pipeline"   => $this->getMiddlewareConfig()
        ];
    }
    /**
     * Return dependencies mapping for this module.
     * We recommend using fully-qualified class names whenever possible as service names.
     *
     * @return array
     */
    public function getServiceConfig()
    {
        return [
            // Use 'invokables' for constructor-less services,
            // or services that do not require arguments to the constructor.
            //
            // Map a service name to the class name.
            // Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class,
            'invokables'    => [                
                Helper\ServerUrlHelper::class => Helper\ServerUrlHelper::class,
            ],
            // Use 'factories' for services provided by callbacks/factory classes.
            'factories'     => [
                // Application
                Application::class      => Container\ApplicationFactory::class,
                Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
                // Middlewares
                Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class,
                Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class,
            ]
        ];
    }
    /**
     * An array of middleware to register. Each item is of the following specification:
     *
     * <code>
     * [
     *  Required:
     *      'middleware' => 'Name or array of names of middleware services and/or callables',
     *  Optional:
     *      'path'     => '/path/to/match', // string; literal path prefix to match
     *                                      // middleware will not execute if path does not match!
     *      'error'    => true, // boolean; true for error middleware
     *      'priority' => 1, // int; higher values == register early;
     *                      // lower/negative == register last;
     *                      // default is 1, if none is provided.
     * ]
     * </code>
     *
     * While the ApplicationFactory ignores the keys associated with specifications, they can be used to allow merging
     * related values defined in multiple configuration files/locations.
     *
     * This defines some conventional keys for middleware to execute early, routing middleware, and error middleware.
     *
     * @return array
     */
    public function getMiddlewareConfig()
    {
        return [
            /**
             * Add more middleware here that you want to execute on every request:
             * - bootstrapping
             * - pre-conditions
             * - modifications to outgoing responses
             */
            'always' => [
                'middleware' => [
                    Helper\ServerUrlMiddleware::class,
                ],
                'priority' => 10000,
            ],
            /**
             * Add more middleware here that needs to introspect the routing results; this might include:
             * - route-based authentication
             * - route-based validation
             * - etc.
             */
            'routing' => [
                'middleware' => [
                    Container\ApplicationFactory::ROUTING_MIDDLEWARE,
                    Helper\UrlHelperMiddleware::class,
                    // Append here
                    Container\ApplicationFactory::DISPATCH_MIDDLEWARE,
                ],
                'priority' => 1,
            ],
            'error' => [
                'middleware' => [
                    //Add more error handler middleware
                ],
                'error'    => true,
                'priority' => -10000,
            ]
        ];
    }
}

See the initial implementation (without test) https://github.com/CodingMatters/zend-expressive-config-provider

geerteltink commented 7 years ago

@gabbydgab Thanx for the suggestion. This feature is included in #54.