@asgrim
Kicking off with Zend Expressive
and Doctrine ORM
James Titcumb
php[MiNDS] March 2018
Follow along (optional): https://github.com/asgrim/book-library
$ whoami
James Titcumb
www.jamestitcumb.com
www.roave.com
@asgrim
@asgrim
What is Zend Expressive?
@asgrim
Layers of an Expressive application
Expressive
Stratigility
Diactoros
PSR-7 Interface
DIRouter Template
Your Application
@asgrim
Layers of an Expressive application
Expressive
Stratigility
Diactoros
PSR-7 Interface
DIRouter Template
Your Application
@asgrim
PSR-7
HTTP Message Interfaces
@asgrim
HTTP Request
POST /talk HTTP/1.1
Host: phpminds.org
foo=bar&baz=bat
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
Layers of an Expressive application
Expressive
Stratigility
Diactoros
PSR-7 Interface
DIRouter Template
Your Application
@asgrim
Zend Diactoros
PSR-7 implementation
@asgrim
Node http.Server using Diactoros
$server = ZendDiactorosServer::createServer(
function ($request, $response, $done) {
return $response->getBody()->write('hello world');
},
$_SERVER,
$_GET,
$_POST,
$_COOKIE,
$_FILES
);
$server->listen();
@asgrim
Layers of an Expressive application
Expressive
Stratigility
Diactoros
PSR-7 Interface
DIRouter Template
Your Application
@asgrim
Zend Stratigility
Creating & dispatching middleware pipelines
@asgrim
So what is “middleware”?
@asgrim
Middleware in the middle
function(Request $request, RequestHandler $handler) : Response
{
// ... some code before ...
$response = $handler->handle($request);
// ... some code after ...
return $response;
}
@asgrim
Some people call it an onion
SessionInitialisingMiddleware
AuthenticationMiddleware
ThingyMiddleware
DashboardHandler
@asgrim
Some people call it an onion
SessionInitialisingMiddleware
AuthenticationMiddleware
ThingyMiddleware
DashboardHandler
@asgrim
Some people call it an onion
SessionInitialisingMiddleware
AuthenticationMiddleware
ThingyMiddleware
DashboardHandler
@asgrim
psr/http-server-middleware
@asgrim
Middleware using MiddlewareInterface
<?php
declare(strict_types=1);
namespace AppMiddleware;
use PsrHttpMessageResponseInterface;
use PsrHttpMessageServerRequestInterface;
use PsrHttpServerMiddlewareInterface;
use PsrHttpServerRequestHandlerInterface;
final class MyMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $requestHandler
) : ResponseInterface {
return $requestHandler>handle($request);
}
}
@asgrim
Double-pass middleware
public function __invoke(
Request $request,
Response $response,
callable $next
): Response {
return $next($request, $response);
}
!!! DEPRECATED !!!
@asgrim
Modifying the response
function(Request $request, RequestHandler $handler) : Response
{
$response = $handler->handle($request);
return $response->withHeader(
'X-Clacks-Overhead',
'GNU Terry Pratchett'
);
}
@asgrim
Pipe all the things!
$pipe = new ZendStratigilityMiddlewarePipe();
$pipe->pipe($logUncaughtErrorsMiddleware);
$pipe->pipe($sessionInitialisingMiddleware);
$pipe->pipe($authenticationMiddleware);
$pipe->pipe('/', $indexHandlerMiddleware);
$pipe->pipe(new NotFoundHandler(...));
@asgrim
LogErrorsCaughtMiddleware
function(Request $request, RequestHandler $handler) : Response
{
try {
return $handler->handle($request);
} catch (Throwable $throwable) {
// Log the error, handle it with error page etc.
return new JsonResponse(
[
'message' => $throwable->getMessage(),
],
500
);
}
}
@asgrim
SessionInitialisingMiddleware
function(Request $request, RequestHandler $handler) : Response
{
session_start();
return $handler->handle($request);
}
@asgrim
function(Request $request, RequestHandler $handler) : Response
{
if (!$this->doSomeOAuthCheck($request)) {
return new JsonResponse(
[
'message' => 'Invalid OAuth token',
],
403
);
}
return $handler->handle($request);
}
AuthenticationMiddleware
@asgrim
IndexHandlerMiddleware
function(Request $request, RequestHandler $handler) : Response
{
// ... some code ...
return new JsonResponse($someData, 200);
}
@asgrim
Layers of an Expressive application
Expressive
Stratigility
Diactoros
PSR-7 Interface
DIRouter Template
Your Application
@asgrim
Zend Expressive
One Ring to bring them all and in the darkness bind them.
@asgrim
Routing
@asgrim
container-interop
@asgrim
Optionally, templating
@asgrim
Piping and Routing
@asgrim
Zend Framework 2/3
What of them?
@asgrim
Middleware vs MVC
@asgrim
Using ZF components
in Expressive
@asgrim
ZF’s Module.php
class Module
{
public function getConfig()
{
return include __DIR__ . '/../config/module.config.php';
}
}
@asgrim
ConfigProvider
@asgrim
ConfigProvider
namespace ZendForm;
class ConfigProvider
{
public function __invoke()
{
return [
'dependencies' => $this->getDependencyConfig(),
'view_helpers' => $this->getViewHelperConfig(),
];
}
}
@asgrim
ConfigProvider#getDependencyConfig()
public function getDependencyConfig()
{
return [
'abstract_factories' => [
FormAbstractServiceFactory::class,
],
'aliases' => [
'ZendFormAnnotationFormAnnotationBuilder' => 'FormAnnotationBuilder',
AnnotationAnnotationBuilder::class => 'FormAnnotationBuilder',
FormElementManager::class => 'FormElementManager',
],
'factories' => [
'FormAnnotationBuilder' => AnnotationAnnotationBuilderFactory::class,
'FormElementManager' => FormElementManagerFactory::class,
],
@asgrim
ZendForm’s Module.php (for Zend Framework)
class Module
{
public function getConfig()
{
$provider = new ConfigProvider();
return [
'service_manager' => $provider->getDependencyConfig(),
'view_helpers' => $provider->getViewHelperConfig(),
];
}
}
@asgrim
config/config.php
<?php
$aggregator = new ConfigAggregator([
ZendFormConfigProvider::class,
// .. other config ...
], $cacheConfig['config_cache_path']);
return $aggregator->getMergedConfig();
@asgrim
ConfigAggregator
@asgrim
config/config.php
$aggregator = new ConfigAggregator([
ZendExpressiveRouterFastRouteRouterConfigProvider::class,
ZendHttpHandlerRunnerConfigProvider::class,
new ArrayProvider($cacheConfig),
ZendExpressiveHelperConfigProvider::class,
ZendExpressiveConfigProvider::class,
ZendExpressiveRouterConfigProvider::class,
AppConfigProvider::class,
new PhpFileProvider(realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php'),
new PhpFileProvider(realpath(__DIR__) . '/development.config.php'),
], $cacheConfig['config_cache_path']);
@asgrim
Layers of an Expressive application
Expressive
Stratigility
Diactoros
PSR-7 Interface
DIRouter Template
Your Application
@asgrim
Getting started
with Zend Expressive
@asgrim
https://github.com/asgrim/book-library
@asgrim
Expressive Skeleton
@asgrim
Expressive installer - start
composer create-project zendframework/zend-expressive-skeleton:3.0.0-rc1 book-library
Installing zendframework/zend-expressive-skeleton (3.0.0rc1)
- Installing zendframework/zend-expressive-skeleton (3.0.0rc1): Downloading (100%)
Created project in test
> ExpressiveInstallerOptionalPackages::install
Setting up optional packages
Setup data and cache dir
Removing installer development dependencie
@asgrim
Expressive installer - structure
What type of installation would you like?
[1] Minimal (no default middleware, templates, or assets; configuration only)
[2] Flat (flat source code structure; default selection)
[3] Modular (modular source code structure; recommended)
Make your selection (2): 2
- Copying src/App/ConfigProvider.php
@asgrim
Modular structure
zend-config-aggregator
@asgrim
Modular structure
zend-config-aggregator
@asgrim
Flat or Modular?
@asgrim
Default modular structure
src/
MyModule/
src/
ConfigProvider.php
Handler/
Entity/
Middleware/
templates/
test/
@asgrim
Expressive installer - container?
Which container do you want to use for dependency injection?
[1] Aura.Di
[2] Pimple
[3] zend-servicemanager
[4] Auryn
[5] Symfony DI Container
Make your selection or type a composer package name and version (zend-servicemanager):
- Adding package zendframework/zend-servicemanager (^3.3)
- Copying config/container.php
@asgrim
Expressive installer - router?
Which router do you want to use?
[1] Aura.Router
[2] FastRoute
[3] zend-router
Make your selection or type a composer package name and version (FastRoute):
- Adding package zendframework/zend-expressive-fastroute (^3.0.0alpha1)
- Whitelist package zendframework/zend-expressive-fastroute
- Copying config/routes.php
@asgrim
Expressive installer - template?
Which template engine do you want to use?
[1] Plates
[2] Twig
[3] zend-view installs zend-servicemanager
[n] None of the above
Make your selection or type a composer package name and version (n):
@asgrim
Expressive installer - whoops?
Which error handler do you want to use during development?
[1] Whoops
[n] None of the above
Make your selection or type a composer package name and version (Whoops): n
@asgrim
Expressive installer - run it!
$ composer serve
> php -S 0.0.0.0:8080 -t public/ public/index.php
[Thu Sep 1 20:29:33 2016] 127.0.0.1:48670 [200]: /favicon.ico
{
"welcome": "Congratulations! You have installed the zend-expressive skeleton application.",
"docsUrl": "https://docs.zendframework.com/zend-expressive/"
}
@asgrim
Create the endpoints
@asgrim
Book entity
class Book
{
/**
* @var string
*/
private $id;
/**
* @var bool
*/
private $inStock = true;
public function __construct()
{
$this->id = (string)Uuid::uuid4();
}
@asgrim
Book entity
class Book
{
/**
* @return void
* @throws AppEntityExceptionBookNotAvailable
*/
public function checkOut()
{
if (!$this->inStock) {
throw ExceptionBookNotAvailable::fromBook($this);
}
$this->inStock = false;
}
@asgrim
Book entity
class Book
{
/**
* @return void
* @throws AppEntityExceptionBookAlreadyStocked
*/
public function checkIn()
{
if ($this->inStock) {
throw ExceptionBookAlreadyStocked::fromBook($this);
}
$this->inStock = true;
}
@asgrim
FindBookByUuidInterface
interface FindBookByUuidInterface
{
/**
* @param UuidInterface $slug
* @return Book
* @throws ExceptionBookNotFound
*/
public function __invoke(UuidInterface $slug): Book;
}
@asgrim
CheckOutHandler
public function process(ServerRequestInterface $request, RequestHandler $handler): JsonResponse
{
try {
$book = $this->findBookByUuid->__invoke(Uuid::fromString($request->getAttribute('id')));
} catch (BookNotFound $bookNotFound) {
return new JsonResponse(['info' => $bookNotFound->getMessage()], 404);
}
try {
$book->checkOut();
} catch (BookNotAvailable $bookNotAvailable) {
return new JsonResponse(['info' => $bookNotAvailable->getMessage()], 423);
}
return new JsonResponse([
'info' => sprintf('You have checked out %s', $book->getId()),
]);
}
@asgrim
return function (
Application $app,
MiddlewareFactory $factory,
ContainerInterface $container
): void {
$app->pipe(ZendStratigilityMiddlewareErrorHandler::class);
$app->pipe(ZendExpressiveRouterMiddlewarePathBasedRoutingMiddleware::class); // Routing
$app->pipe(ZendExpressiveRouterMiddlewareDispatchMiddleware::class); // Dispatch
$app->pipe(ZendExpressiveHandlerNotFoundHandler::class);
};
Define application pipeline - pipeline.php
@asgrim
return function (
Application $app,
MiddlewareFactory $factory,
ContainerInterface $container
): void {
$app->get(
'/book/{id}/check-out',
AppHandlerCheckOutHandler::class,
'check-out'
);
$app->get(
'/book/{id}/check-in',
AppHandlerCheckInHandler::class,
'check-in'
);
};
Define routes - routes.php
@asgrim
Adding some ORM
@asgrim
Your application
Doctrine quick overview
DB
DBAL
ORM
(EntityManager)
Entities
Finders,
Services,
...
@asgrim
container-interop-doctrine
@asgrim
Installation
$ composer require dasprid/container-interop-doctrine
Using version ^1.1 for dasprid/container-interop-doctrine
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 9 installs, 0 updates, 0 removals
- Installing doctrine/lexer (v1.0.1): Loading from cache
- Installing doctrine/inflector (v1.3.0): Loading from cache
- Installing doctrine/collections (v1.5.0): Loading from cache
- Installing doctrine/cache (v1.7.1): Loading from cache
- Installing doctrine/annotations (v1.6.0): Loading from cache
- Installing doctrine/common (v2.8.1): Loading from cache
- Installing doctrine/dbal (v2.6.3): Loading from cache
- Installing doctrine/orm (v2.6.1): Loading from cache
- Installing dasprid/container-interop-doctrine (1.1.0): Loading from cache
Writing lock file
Generating autoload files
@asgrim
src/App/ConfigProvider.php
use DoctrineORMEntityManagerInterface;
use ContainerInteropDoctrineEntityManagerFactory;
final class ConfigProvider
{
// ...
public function getDependencies() : array
{
return [
'factories' => [
EntityManagerInterface::class => EntityManagerFactory::class,
// ... other services
],
];
@asgrim
'doctrine' => [
'connection' => [
'orm_default' => [
'driver_class' => PDOPgSqlDriver::class,
'params' => [
'url' => 'postgres://user:pass@localhost/book_library',
],
],
],
'driver' => [
'orm_default' => [
'class' => MappingDriverChain::class,
'drivers' => [
// ... and so on ...
config/autoload/doctrine.global.php
@asgrim
'doctrine' => [
'connection' => [
'orm_default' => [
'driver_class' => PDOPgSqlDriver::class,
'params' => [
'url' => 'postgres://user:pass@localhost/book_library',
],
],
],
'driver' => [
'orm_default' => [
'class' => MappingDriverChain::class,
'drivers' => [
// ... and so on ...
config/autoload/doctrine.global.php
@asgrim
<?php
declare(strict_types = 1);
use DoctrineORMEntityManagerInterface;
use DoctrineORMToolsConsoleHelperEntityManagerHelper;
use SymfonyComponentConsoleHelperHelperSet;
$container = require __DIR__ . '/container.php';
return new HelperSet([
'em' => new EntityManagerHelper(
$container->get(EntityManagerInterface::class)
),
]);
config/cli-config.php
@asgrim
Annotating the Entities
@asgrim
/**
* @ORMEntity
* @ORMTable(name="book")
*/
class Book
{
/**
* @ORMId
* @ORMColumn(name="id", type="guid")
* @ORMGeneratedValue(strategy="NONE")
* @var string
*/
private $id;
src/App/Entity/Book.php
@asgrim
/**
* @ORMEntity
* @ORMTable(name="book")
*/
class Book
{
/**
* @ORMId
* @ORMColumn(name="id", type="guid")
* @ORMGeneratedValue(strategy="NONE")
* @var string
*/
private $id;
src/App/Entity/Book.php
@asgrim
public function __invoke(UuidInterface $id): Book
{
/** @var Book|null $book */
$book = $this->repository->find((string)$id);
if (null === $book) {
throw ExceptionBookNotFound::fromUuid($id);
}
return $book;
}
src/App/Service/Book/DoctrineFindBookByUuid.php
@asgrim
try {
$this->entityManager->transactional(function () use ($book) {
$book->checkOut();
});
} catch (BookNotAvailable $bookNotAvailable) {
return new JsonResponse(['info' => $bookNotAvailable->getMessage()], 423);
}
src/App/Handler/CheckOutHandler.php
@asgrim
Generate the schema
$ vendor/bin/doctrine orm:schema-tool:create
ATTENTION: This operation should not be executed in a production
environment.
Creating database schema...
Database schema created successfully!
$
@asgrim
Insert some data
INSERT INTO book (id, name, in_stock) VALUES (
'1c06bec9-adae-47c2-b411-73b1db850e6f',
'The Great Escape',
true
);
@asgrim
/book/1c06bec9-adae-47c2-b411-.../check-out
{"info":"You have checked out The Great Escape"}
@asgrim
/book/1c06bec9-adae-47c2-b411-.../check-in
{"info":"You have checked in The Great Escape"}
@asgrim
Automatic Flushing Middleware
@asgrim
FlushingMiddleware
public function process(Request $request, RequestHandler $handler)
{
$response = $handler->handle($request);
if ($this->entityManager->isOpen()) {
$this->entityManager->flush();
}
return $response;
}
@asgrim
FlushingMiddleware
public function process(Request $request, RequestHandler $handler)
{
$response = $handler->handle($request);
if ($this->entityManager->isOpen()) {
$this->entityManager->flush();
}
return $response;
}
@asgrim
FlushingMiddleware
public function process(Request $request, RequestHandler $handler)
{
$response = $handler->handle($request);
if ($this->entityManager->isOpen()) {
$this->entityManager->flush();
}
return $response;
}
@asgrim
Add to pipeline
return function (
Application $app,
MiddlewareFactory $factory,
ContainerInterface $container
): void {
$app->pipe(ZendStratigilityMiddlewareErrorHandler::class);
$app->pipe(ZendExpressiveRouterMiddlewarePathBasedRoutingMiddleware::class); // Routing
$app->pipe(AppMiddlewareFlushingMiddleware::class);
$app->pipe(ZendExpressiveRouterMiddlewareDispatchMiddleware::class); // Dispatch
$app->pipe(ZendExpressiveHandlerNotFoundHandler::class);
};
@asgrim
Doing more with
middleware
@asgrim
Authentication
@asgrim
public function process(
ServerRequestInterface $request,
RequestHandler $handler
) : Response {
$queryParams = $request->getQueryParams();
// DO NOT DO THIS IN REAL LIFE
// It's really not secure ;)
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(['error' => 'You are not authenticated.'], 403);
}
return $handler->handle($request);
}
Create the middleware
@asgrim
public function process(
ServerRequestInterface $request,
RequestHandler $handler
) : Response {
$queryParams = $request->getQueryParams();
// DO NOT DO THIS IN REAL LIFE
// It's really not secure ;)
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(['error' => 'You are not authenticated.'], 403);
}
return $handler->handle($request);
}
Create the middleware
@asgrim
public function process(
ServerRequestInterface $request,
RequestHandler $handler
) : Response {
$queryParams = $request->getQueryParams();
// DO NOT DO THIS IN REAL LIFE
// It's really not secure ;)
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(['error' => 'You are not authenticated.'], 403);
}
return $handler->handle($request);
}
Create the middleware
@asgrim
public function process(
ServerRequestInterface $request,
RequestHandler $handler
) : Response {
$queryParams = $request->getQueryParams();
// DO NOT DO THIS IN REAL LIFE
// It's really not secure ;)
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(['error' => 'You are not authenticated.'], 403);
}
return $handler->handle($request);
}
Create the middleware
@asgrim
return function (
Application $app,
MiddlewareFactory $factory,
ContainerInterface $container
): void {
$app->pipe(ZendStratigilityMiddlewareErrorHandler::class);
$app->pipe(ZendExpressiveRouterMiddlewarePathBasedRoutingMiddleware::class); // Routing
$app->pipe(AppMiddlewareFlushingMiddleware::class);
$app->pipe(AppMiddlewareAuthenticationMiddleware::class);
$app->pipe(ZendExpressiveRouterMiddlewareDispatchMiddleware::class); // Dispatch
$app->pipe(ZendExpressiveHandlerNotFoundHandler::class);
};
Add middleware to pipe
@asgrim
Helios
composer require dasprid/helios
@asgrim
PSR7-Session
composer require psr7-sessions/storageless
@asgrim
public function __invoke(ContainerInterface $container, $_, array $_ = null)
{
$symmetricKey = 'do-not-store-this-key-in-git-store-it-in-configuration-instead-please';
$expirationTime = 1200; // 20 minutes
return new SessionMiddleware(
new SignerHmacSha256(),
$symmetricKey,
$symmetricKey,
SetCookie::create(SessionMiddleware::DEFAULT_COOKIE)
->withSecure(false) // false on purpose, unless you have https locally
->withHttpOnly(true)
->withPath('/'),
new Parser(),
$expirationTime,
new SystemClock()
);
}
Factory the middleware
@asgrim
return function (
Application $app,
MiddlewareFactory $factory,
ContainerInterface $container
): void {
$app->pipe(ZendStratigilityMiddlewareErrorHandler::class);
$app->pipe(ZendExpressiveRouterMiddlewarePathBasedRoutingMiddleware::class); // Routing
$app->pipe(AppMiddlewareFlushingMiddleware::class);
$app->pipe(PSR7SessionsStoragelessHttpSessionMiddleware::class);
$app->pipe(AppMiddlewareAuthenticationMiddleware::class);
$app->pipe(ZendExpressiveRouterMiddlewareDispatchMiddleware::class); // Dispatch
$app->pipe(ZendExpressiveHandlerNotFoundHandler::class);
};
Add middleware to pipe
@asgrim
$session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
$session->set('counter', $session->get('counter', 0) + 1);
Session is stored in Request
@asgrim
$skills++;
@asgrim
To summarise...
● PSR-7 & PSR-15 lay the foundations!
● Diactoros is just a PSR-7 implementation
● Stratigility is a middleware pipeline: the main bit
● Expressive is a glue for everything
● ConfigProvider is great for aggregating (merging) config
● container-interop-doctrine makes Doctrine work easier
● Middleware all the things!
● Similar concepts in other frameworks (e.g. Slim)
@asgrim
Useful references
● https://github.com/asgrim/book-library
● https://framework.zend.com/blog/2017-12-14-expressive-3-dev.html
● https://framework.zend.com/blog/2017-03-13-expressive-2-migration.html
● https://framework.zend.com/blog/2017-03-30-expressive-config-routes.html
● https://mwop.net/blog/2016-05-16-programmatic-expressive.html
● https://docs.zendframework.com/zend-expressive/
Any questions?
James Titcumb
@asgrim

More Related Content

PDF
Kicking off with Zend Expressive and Doctrine ORM (PHP South Africa 2018)
PDF
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
KEY
The use of Symfony2 @ Overblog
PDF
Tame cloud complexity with F# powered DSLs (build stuff)
PDF
Conscious Decoupling - Lone Star PHP
PDF
Documenting from the Trenches
ODP
Aspect-oriented programming in Perl
PDF
SPIFFE Meetup Tokyo #2 - Attestation Internals in SPIRE - Shingo Omura
Kicking off with Zend Expressive and Doctrine ORM (PHP South Africa 2018)
Kicking off with Zend Expressive and Doctrine ORM (PHP Srbija 2017)
The use of Symfony2 @ Overblog
Tame cloud complexity with F# powered DSLs (build stuff)
Conscious Decoupling - Lone Star PHP
Documenting from the Trenches
Aspect-oriented programming in Perl
SPIFFE Meetup Tokyo #2 - Attestation Internals in SPIRE - Shingo Omura

What's hot (20)

PPTX
Function pointer
PDF
Resting with OroCRM Webinar
PDF
OroCRM Partner Technical Training: September 2015
PPTX
Catch and Release: A New Look at Detecting and Mitigating highly obfuscated E...
PDF
Customizing oro crm webinar
PDF
Working with oro crm entities
PPTX
POLITEKNIK MALAYSIA
PPTX
Python Programming Essentials - M19 - Namespaces, Global Variables and Docstr...
PDF
Java EE 6 CDI Integrates with Spring & JSF
PDF
Sun certifiedwebcomponentdeveloperstudyguide
PPTX
Terraform Abstractions for Safety and Power
PPTX
Comprehensive Terraform Training
PDF
Intro to JavaScript
PDF
PHP 8: What's New and Changed
KEY
A Conversation About REST
PDF
Proxy Deep Dive Voxxed Belgrad 2015
PDF
Functional Javascript
PDF
Terraform - Taming Modern Clouds
PDF
Infrastructure as Code with Terraform
PDF
Adding custom ui controls to your application (1)
Function pointer
Resting with OroCRM Webinar
OroCRM Partner Technical Training: September 2015
Catch and Release: A New Look at Detecting and Mitigating highly obfuscated E...
Customizing oro crm webinar
Working with oro crm entities
POLITEKNIK MALAYSIA
Python Programming Essentials - M19 - Namespaces, Global Variables and Docstr...
Java EE 6 CDI Integrates with Spring & JSF
Sun certifiedwebcomponentdeveloperstudyguide
Terraform Abstractions for Safety and Power
Comprehensive Terraform Training
Intro to JavaScript
PHP 8: What's New and Changed
A Conversation About REST
Proxy Deep Dive Voxxed Belgrad 2015
Functional Javascript
Terraform - Taming Modern Clouds
Infrastructure as Code with Terraform
Adding custom ui controls to your application (1)
Ad

Similar to Kicking off with Zend Expressive and Doctrine ORM (PHP MiNDS March 2018) (20)

PDF
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
PDF
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
PDF
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
PDF
Kicking off with Zend Expressive and Doctrine ORM (ZendCon 2016)
PDF
Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
PDF
Ростислав Михайлив "Zend Framework 3 - evolution or revolution"
PDF
Make your application expressive
PDF
Zend/Expressive 3 – The Next Generation
PDF
Zend expressive workshop
PDF
Expressive Microservice Framework Blastoff
PDF
Quick start on Zend Framework 2
PDF
A quick start on Zend Framework 2
ODP
Creating Web Services with Zend Framework - Matthew Turland
PPT
Demo
PPT
Greenathan
PPT
Unit Test for ZF SlideShare Component
PPT
Unit Test for ZF SlideShare Component
PPT
Unit Test for ZF SlideShare Component
PPT
PPT
first pitch
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
Kicking off with Zend Expressive and Doctrine ORM (Sunshine PHP 2017)
Kicking off with Zend Expressive and Doctrine ORM (ZendCon 2016)
Kicking off with Zend Expressive and Doctrine ORM (PHPNW2016)
Ростислав Михайлив "Zend Framework 3 - evolution or revolution"
Make your application expressive
Zend/Expressive 3 – The Next Generation
Zend expressive workshop
Expressive Microservice Framework Blastoff
Quick start on Zend Framework 2
A quick start on Zend Framework 2
Creating Web Services with Zend Framework - Matthew Turland
Demo
Greenathan
Unit Test for ZF SlideShare Component
Unit Test for ZF SlideShare Component
Unit Test for ZF SlideShare Component
first pitch
Ad

More from James Titcumb (20)

PDF
Living the Best Life on a Legacy Project (phpday 2022).pdf
PDF
Tips for Tackling a Legacy Codebase (ScotlandPHP 2021)
PDF
Climbing the Abstract Syntax Tree (Midwest PHP 2020)
PDF
Best practices for crafting high quality PHP apps (Bulgaria 2019)
PDF
Climbing the Abstract Syntax Tree (php[world] 2019)
PDF
Best practices for crafting high quality PHP apps (php[world] 2019)
PDF
Crafting Quality PHP Applications (PHP Joburg Oct 2019)
PDF
Climbing the Abstract Syntax Tree (PHP Russia 2019)
PDF
Best practices for crafting high quality PHP apps - PHP UK 2019
PDF
Climbing the Abstract Syntax Tree (ScotlandPHP 2018)
PDF
Best practices for crafting high quality PHP apps (ScotlandPHP 2018)
PDF
Best practices for crafting high quality PHP apps (PHP South Africa 2018)
PDF
Climbing the Abstract Syntax Tree (PHP Developer Days Dresden 2018)
PDF
Climbing the Abstract Syntax Tree (Southeast PHP 2018)
PDF
Crafting Quality PHP Applications (PHPkonf 2018)
PDF
Best practices for crafting high quality PHP apps (PHP Yorkshire 2018)
PDF
Crafting Quality PHP Applications: an overview (PHPSW March 2018)
PDF
Climbing the Abstract Syntax Tree (PHP UK 2018)
PDF
Crafting Quality PHP Applications (PHP Benelux 2018)
PDF
Crafting Quality PHP Applications (ConFoo YVR 2017)
Living the Best Life on a Legacy Project (phpday 2022).pdf
Tips for Tackling a Legacy Codebase (ScotlandPHP 2021)
Climbing the Abstract Syntax Tree (Midwest PHP 2020)
Best practices for crafting high quality PHP apps (Bulgaria 2019)
Climbing the Abstract Syntax Tree (php[world] 2019)
Best practices for crafting high quality PHP apps (php[world] 2019)
Crafting Quality PHP Applications (PHP Joburg Oct 2019)
Climbing the Abstract Syntax Tree (PHP Russia 2019)
Best practices for crafting high quality PHP apps - PHP UK 2019
Climbing the Abstract Syntax Tree (ScotlandPHP 2018)
Best practices for crafting high quality PHP apps (ScotlandPHP 2018)
Best practices for crafting high quality PHP apps (PHP South Africa 2018)
Climbing the Abstract Syntax Tree (PHP Developer Days Dresden 2018)
Climbing the Abstract Syntax Tree (Southeast PHP 2018)
Crafting Quality PHP Applications (PHPkonf 2018)
Best practices for crafting high quality PHP apps (PHP Yorkshire 2018)
Crafting Quality PHP Applications: an overview (PHPSW March 2018)
Climbing the Abstract Syntax Tree (PHP UK 2018)
Crafting Quality PHP Applications (PHP Benelux 2018)
Crafting Quality PHP Applications (ConFoo YVR 2017)

Recently uploaded (20)

PPTX
MicrosoftCybserSecurityReferenceArchitecture-April-2025.pptx
PDF
CloudStack 4.21: First Look Webinar slides
PDF
ENT215_Completing-a-large-scale-migration-and-modernization-with-AWS.pdf
PPTX
Benefits of Physical activity for teenagers.pptx
PDF
Getting started with AI Agents and Multi-Agent Systems
PDF
Five Habits of High-Impact Board Members
PDF
August Patch Tuesday
PDF
Transform Your ITIL® 4 & ITSM Strategy with AI in 2025.pdf
PDF
Microsoft Solutions Partner Drive Digital Transformation with D365.pdf
PDF
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
PPT
Geologic Time for studying geology for geologist
PDF
DASA ADMISSION 2024_FirstRound_FirstRank_LastRank.pdf
PDF
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
PDF
1 - Historical Antecedents, Social Consideration.pdf
PDF
Univ-Connecticut-ChatGPT-Presentaion.pdf
PDF
A contest of sentiment analysis: k-nearest neighbor versus neural network
PDF
Enhancing emotion recognition model for a student engagement use case through...
PPTX
The various Industrial Revolutions .pptx
PDF
Unlock new opportunities with location data.pdf
PPTX
O2C Customer Invoices to Receipt V15A.pptx
MicrosoftCybserSecurityReferenceArchitecture-April-2025.pptx
CloudStack 4.21: First Look Webinar slides
ENT215_Completing-a-large-scale-migration-and-modernization-with-AWS.pdf
Benefits of Physical activity for teenagers.pptx
Getting started with AI Agents and Multi-Agent Systems
Five Habits of High-Impact Board Members
August Patch Tuesday
Transform Your ITIL® 4 & ITSM Strategy with AI in 2025.pdf
Microsoft Solutions Partner Drive Digital Transformation with D365.pdf
Video forgery: An extensive analysis of inter-and intra-frame manipulation al...
Geologic Time for studying geology for geologist
DASA ADMISSION 2024_FirstRound_FirstRank_LastRank.pdf
How ambidextrous entrepreneurial leaders react to the artificial intelligence...
1 - Historical Antecedents, Social Consideration.pdf
Univ-Connecticut-ChatGPT-Presentaion.pdf
A contest of sentiment analysis: k-nearest neighbor versus neural network
Enhancing emotion recognition model for a student engagement use case through...
The various Industrial Revolutions .pptx
Unlock new opportunities with location data.pdf
O2C Customer Invoices to Receipt V15A.pptx

Kicking off with Zend Expressive and Doctrine ORM (PHP MiNDS March 2018)