Skip to content

Instantly share code, notes, and snippets.

@tdutrion
Last active May 16, 2018 09:32
Show Gist options
  • Save tdutrion/9d5e2466a843a62cbfb60f665abe4e0a to your computer and use it in GitHub Desktop.
Save tdutrion/9d5e2466a843a62cbfb60f665abe4e0a to your computer and use it in GitHub Desktop.
ZF3 and Doctrine get started - Florent Blaison

Setup the project

Official tutorial from which this is adapted:

Get the project skeleton

Clone the project from Florent's repository (updated ZF3 quickstart):]

git clone [email protected]:Orkin/tsi-getting-start.git

Install the dependencies

composer update

Choose option 0 when asked Make your selection (default is 0): (press enter as it is the default option).

When asked Remember this option for other packages of the same type? (y/N), type y and press enter.

Database setup

Create an album database and set your database access settings in the configuration file.

CREATE TABLE `album` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `artist` varchar(100) NOT NULL DEFAULT '',
  `title` varchar(100) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Set your configuration credentials in the configuration.

The config/autoload/global.php already contains the following code:

<?php
/**
 * Global Configuration Override
 *
 * You can use this file for overriding configuration values from modules, etc.
 * You would place values in here that are agnostic to the environment and not
 * sensitive to security.
 *
 * @NOTE: In practice, this file will typically be INCLUDED in your source
 * control, so do not include passwords or other sensitive information in this
 * file.
 */

return [
    'db' => [
        'driver' => 'Pdo',
        'dsn'    => 'mysql:dbname=album;host=localhost',
        'username' => 'root',
        'password' => '',
    ],
];

Add your own config in config/autoload/local.php if needed:

<?php

return [
    'db' => [
        'username' => 'myuser',
        'password' => 'mypassword',
    ],
];

Development mode

Activate the development mode in ZF3:

composer development-enable

This command copy config/development.config.php.dist to development.config.php, hence allowing config file in autoload/{,*.}{global,local}-development.php. It also disable the config cache.

Configuration

Modules

modules.config.php ZF2.5+ splitted in components that need to be activated individually in this file

Zend Developer Tools => gives details about the request, acl, db, etc. ZDT does not work for API development

Order of the files: global first, local next. Local files are not pushed to the repository (db connection and other secrets).

Read index.php:

  • load an optional development config
  • load the main config (application)

Both config contain instructions to retrieve config files from the autoload folder.

Zend Developer Tools quick start: copy configuration to the autoload folder.

Details the db configuration: how to set your local config

Composer scripts: development-enable, code style and so on.

Launch project using composer serve.

Modules

Details module syntax:

  • config
  • src
  • view

Best practice: separate config files by types to separate concerns

module.config.php:

return [
    'routes' => include 'routes.php',
    'service_manager' => include 'service_manager.php',
];

routes.php:

Explaination of the getting start routes. Details the Application\config\module.config.php.

Demonstration of a 404 by editing the routing. Turn on/off the display_not_found_reason and display exceptions. Explain the default config for 404 error pages and other error pages, and how you can override with a custom view.

template_path_stack easy configuration, good for rad but very bad perf (disk io).

template_map => precedence of the map on the stack, ocramius ocracachedviewresolver, allow easy override with map.

View helper: show paginator view, navbar helper. Only works in the template_map as naming is not specified, no way for the template_path_stack to resolve.

Module.php: mandatory to make the module work. Does not do much necessarly. Read the module config files. Different interfaces exist, providing configuration for the module (ie. ADD FORM ELEMENT PROVIDER INTERFACE NAME HERE).

Explain how config array cannot be cached when containing an anonymous function.

Back on composer for autoloading, with key PSR4. Show composer dump-autoload after removing a psr4 mapping, put it back, still not working, re-dump autoload and working.

Clean the code exercice

The code provided does not contain a service layer, does not specifically follow the conventions.

AlbumController has a strong dependency on the AlbumTable => requires a factory. Demo: remove the table from the factory instanciation, refresh and crash.

The container know how to provide the object. Demonstration of how to chain the factories (Controller => Table => Gateway).

In 'service_manager', register new mapping in the container:

return [
	AlbumTable::class => AlbumTableFactory::class,
	AlbumGateway::class => AlbumGatewayFactory::class,
];

The container (by default), serve the object if already instanciated.

ZF2 had the service_manager invokables key for service with no dependencies. ZF3 introduces an InvokableFactory to push developers to use factory without creating the factory with no code inside.

Exercice: implement CGU in a specific module

Routes:

  • /cgu
  • /legal

Create the structure of your module:

  • /module
    • /Legal
      • /config
      • /src
      • /view

Add the new module to the autoloader:

composer.json (psr4 key):

{
    "name": "zendframework/skeleton-application",
    "description": "Skeleton Application for Zend Framework zend-mvc applications",
    "type": "project",
    "license": "BSD-3-Clause",
    "keywords": [
        "framework",
        "mvc",
        "zf"
    ],
    "homepage": "http://framework.zend.com/",
    "minimum-stability": "dev",
    "prefer-stable": true,
    "require": {
        "php": "^5.6 || ^7.0",
        "zendframework/zend-component-installer": "^1.0 || ^0.3 || ^1.0.0-dev@dev",
        "zendframework/zend-mvc": "^3.0.1",
        "zfcampus/zf-development-mode": "^3.0",
        "zendframework/zend-db": "^2.8.1",
        "zendframework/zend-mvc-form": "^1.0"
    },
    "autoload": {
        "psr-4": {
            "Application\\": "module/Application/src/",
            "Album\\": "module/Album/src/",
            "Blog\\": "module/Blog/src/",
            "Legal\\": "module/Legal/src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "ApplicationTest\\": "module/Application/test/"
        }
    },
    "extra": [],
    "scripts": {
        "cs-check": "phpcs",
        "cs-fix": "phpcbf",
        "development-disable": "zf-development-mode disable",
        "development-enable": "zf-development-mode enable",
        "development-status": "zf-development-mode status",
        "post-create-project-cmd": [
            "@development-enable"
        ],
        "serve": "php -S 0.0.0.0:8080 -t public/ public/index.php",
        "test": "phpunit"
    },
    "require-dev": {
        "zendframework/zend-developer-tools": "^1.1.0"
    }
}

And then composer dump-autoload to activate the PSR4 autoloading change.

Create the controller (module/Legal/src/Controller/LegalController.php):

<?php

declare(strict_types=1);

namespace Legal\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

final class LegalController extends AbstractActionController
{
    public function cguAction()
    {
        return new ViewModel([]);
    }

    public function legalAction()
    {
        return new ViewModel([]);
    }
}

module/Legal/config/module.config.php:

<?php

declare(strict_types=1);

use Legal\Controller\LegalController;
use Zend\Router\Http\Literal;
use Zend\ServiceManager\Factory\InvokableFactory;

return [
    'controllers' => [
        'factories' => [
            LegalController::class => InvokableFactory::class,
        ],
    ],

    'router' => [
        'routes' => include __DIR__.'/routes.config.php',
    ],

    'view_manager' => [
        'template_map' => [
            'legal/legal/cgu' => __DIR__ . '/../view/legal/cgu.phtml',
            'legal/legal/legal' => __DIR__ . '/../view/legal/legal.phtml',
        ],
    ],
];

module/Legal/config/routes.config.php:

<?php

return [
    'legal' => [
        'type' => Literal::class,
        'options' => [
            'route'       => '/legal',
            'defaults'    => [
                'controller' => LegalController::class,
                'action'     => 'legal',
            ],
        ],
    ],
    'cgu' => [
        'type' => Literal::class,
        'options' => [
            'route'       => '/cgu',
            'defaults'    => [
                'controller' => LegalController::class,
                'action'     => 'cgu',
            ],
        ],
	],
],

module/Legal/src/Module.php:

<?php

declare(strict_types=1);

namespace Legal;

use Zend\ModuleManager\Feature\ConfigProviderInterface;

final class Module implements ConfigProviderInterface
{
    public function getConfig()
    {
        return include __DIR__.'/../config/module.config.php';
    }
}

modules.config.php (Legal at the end):

<?php
/**
 * @link      http://github.com/zendframework/ZendSkeletonApplication for the canonical source repository
 * @copyright Copyright (c) 2005-2016 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://framework.zend.com/license/new-bsd New BSD License
 */

/**
 * List of enabled modules for this application.
 *
 * This should be an array of module namespaces used in the application.
 */
return [
    'Zend\Form',
    'Zend\Db',
    'Zend\Router',
    'Zend\Validator',
    'ZendDeveloperTools',
    'Application',
    'Album',
    'Blog',
    'Legal',
];

module/Legal/view/legal/cgu.phtml:

cgu

module/Legal/view/legal/legal.phtml:

legal

Routing

Segment is used in other modules. In this specific case, the route is just a string so Literal.

REQUIRES EXAMPLE HERE.

For SEO reasons, using a semantic path in the URI is a best practice. Therefore, creating a Literal route for each endpoint.

Another solution to have semantic paths in URIs is to use child routes.

'legal' => [
    'type' => Literal::class,
    'options' => [
        'route' => '/legal',
    ],
    'may_terminate' => false,
    'child_routes' => [
        'legal' => [
	        'type' => Literal::class,
            'options' => [
			    'route'       => '/legal',
			    'defaults'    => [
			        'controller' => LegalController::class,
			        'action'     => 'legal',
			    ],
			],
        ],
		'cgu' => [
		    'type' => Literal::class,
			'options' => [
			    'route' => '/cgu',
			    'defaults'    => [
			        'controller' => LegalController::class,
			        'action' => 'cgu',
			    ],
			],
        ],
    ],
],

More than just being an seo trick, having parent/child routes helps with prefixing.

Example with adding /legal/:lang in the child route, passing as a segment. Introducing the controller plugins $this->params(), using the fromRoute method and setting a default inline.

Add lang constraint ('lang' => '[a-zA-Z]*') and defaults.

@wdonline123
Copy link

pls, share source code!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment