SlideShare a Scribd company logo
@asgrim
Kicking off with Zend Expressive
and
Doctrine ORM
James Titcumb
ZendCon 2016
Who is this guy?
James Titcumb
www.jamestitcumb.com
www.roave.com
www.phphants.co.uk
www.phpsouthcoast.co.uk
@asgrim
@asgrim
What is Zend Expressive?
@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 /zendcon2016/foo HTTP/1.1
Host: www.zendcon.com
foo=bar&baz=bat
@asgrim
HTTP Request
POST /zendcon2016/foo HTTP/1.1
Host: www.zendcon.com
foo=bar&baz=bat
@asgrim
HTTP Request
POST /zendcon2016/foo HTTP/1.1
Host: www.zendcon.com
foo=bar&baz=bat
@asgrim
HTTP Request
POST /zendcon2016/foo HTTP/1.1
Host: www.zendcon.com
foo=bar&baz=bat
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
HTTP Response
HTTP/1.1 200 OK
Content-Type: text/plain
This is the response body
@asgrim
Zend Diactoros
PSR-7 implementation
@asgrim
Zend Stratigility
Creating & dispatching middleware pipelines
@asgrim
So what is “middleware”?
@asgrim
Middleware example
public function __invoke(
Request $request,
Response $response,
callable $next = null
) {
// ... some code before ...
$response = $next($request, $response);
// ... some code after ...
return $response;
}
@asgrim
Middleware example
public function __invoke(
Request $request,
Response $response,
callable $next = null
) {
// ... some code before ...
$response = $next($request, $response);
// ... some code after ...
return $response;
}
@asgrim
Middleware example
public function __invoke(
Request $request,
Response $response,
callable $next = null
) {
// ... some code before ...
$response = $next($request, $response);
// ... some code after ...
return $response;
}
@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
Pipe all the things!
$pipe = new ZendStratigilityMiddlewarePipe();
$pipe->pipe($sessionMiddleware);
$pipe->pipe('/foo', $fooMiddleware);
$pipe->pipe($whateverMiddleware);
$pipe->pipe($dogeMiddleware);
@asgrim
Zend Framework 2/3
What of them?
@asgrim
Middleware vs MVC
@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
book-library
Installing zendframework/zend-expressive-skeleton (1.0.3)
- Installing zendframework/zend-expressive-skeleton (1.0.3)
Downloading: 100%
Created project in book-library
> ExpressiveInstallerOptionalPackages::install
Setup data and cache dir
Setting up optional packages
@asgrim
Expressive installer - minimal?
Minimal skeleton? (no default middleware, templates, or assets;
configuration only)
[y] Yes (minimal)
[n] No (full; recommended)
Make your selection (No): n
@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): 2
@asgrim
Expressive installer - container?
Which container do you want to use for dependency injection?
[1] Aura.Di
[2] Pimple
[3] Zend ServiceManager
Make your selection or type a composer package name and version
(Zend ServiceManager): 3
@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): 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": "zend-expressive.readthedocs.org"
}
@asgrim
Create the endpoints
@asgrim
Book entity
final class Book
{
/**
* @var string
*/
private $id;
/**
* @var bool
*/
private $inStock = true;
public function __construct()
{
$this->id = (string)Uuid::uuid4();
}
@asgrim
Book entity
final class Book
{
/**
* @return void
* @throws AppEntityExceptionBookNotAvailable
*/
public function checkOut()
{
if (!$this->inStock) {
throw ExceptionBookNotAvailable::fromBook($this);
}
$this->inStock = false;
}
@asgrim
Book entity
final 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
CheckOutAction
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next = null
) : 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
Adding some ORM
@asgrim
DoctrineORMModule
@asgrim
DoctrineORMModule + Expressive?
@asgrim
config/autoload/doctrine-modules.global.php
$vendorPath = __DIR__ . '/../../vendor';
$doctrineModuleConfig = require_once
$vendorPath . '/doctrine/doctrine-module/config/module.config.php';
$doctrineModuleConfig['dependencies'] = $doctrineModuleConfig['service_manager'];
unset($doctrineModuleConfig['service_manager']);
$ormModuleConfig = require_once
$vendorPath . '/doctrine/doctrine-orm-module/config/module.config.php';
$ormModuleConfig['dependencies'] = $ormModuleConfig['service_manager'];
unset($ormModuleConfig['service_manager']);
return ArrayUtils::merge($doctrineModuleConfig, $ormModuleConfig);
@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
config/autoload/zend-form.global.php
<?php
use ZendFormConfigProvider;
return (new ConfigProvider())->__invoke();
@asgrim
But wait!
@asgrim
container-interop-doctrine
saves the day!!!
@asgrim
Installation
$ composer require dasprid/container-interop-doctrine
Using version ^0.2.2 for dasprid/container-interop-doctrine
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing dasprid/container-interop-doctrine (0.2.2)
Loading from cache
Writing lock file
Generating autoload files
$
@asgrim
config/autoload/doctrine.global.php
<?php
declare(strict_types = 1);
use DoctrineORMEntityManagerInterface;
use ContainerInteropDoctrineEntityManagerFactory;
return [
'dependencies' => [
'factories' => [
EntityManagerInterface::class => EntityManagerFactory::class,
],
],
];
@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
/**
* @ORMEntity
* @ORMTable(name="book")
*/
final 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")
*/
final 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/Action/CheckOutAction.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
Doing more with
middleware
@asgrim
Authentication
@asgrim
public function __invoke(
Request $request, Response $response, callable $next = null
) : Response {
$queryParams = $request->getQueryParams();
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(
['error' => 'You are not authenticated.'],
403
);
}
return $next($request, $response);
}
Create the middleware
@asgrim
public function __invoke(
Request $request, Response $response, callable $next = null
) : Response {
$queryParams = $request->getQueryParams();
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(
['error' => 'You are not authenticated.'],
403
);
}
return $next($request, $response);
}
Create the middleware
@asgrim
public function __invoke(
Request $request, Response $response, callable $next = null
) : Response {
$queryParams = $request->getQueryParams();
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(
['error' => 'You are not authenticated.'],
403
);
}
return $next($request, $response);
}
Create the middleware
@asgrim
public function __invoke(
Request $request, Response $response, callable $next = null
) : Response {
$queryParams = $request->getQueryParams();
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(
['error' => 'You are not authenticated.'],
403
);
}
return $next($request, $response);
}
Create the middleware
@asgrim
public function __invoke(
Request $request, Response $response, callable $next = null
) : Response {
$queryParams = $request->getQueryParams();
if (!array_key_exists('authenticated', $queryParams)
|| $queryParams['authenticated'] !== '1') {
return new JsonResponse(
['error' => 'You are not authenticated.'],
403
);
}
return $next($request, $response);
}
Create the middleware
@asgrim
PSR7-Session
composer require psr7-sessions/storageless
@asgrim
public function __invoke(ContainerInterface $container, $_, array $_ = null)
{
$symmetricKey = 'super-secure-key-you-should-not-store-this-key-in-git';
$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 SystemCurrentTime()
);
}
Factory the middleware
@asgrim
'routing' => [
'middleware' => [
ApplicationFactory::ROUTING_MIDDLEWARE,
HelperUrlHelperMiddleware::class,
PSR7SessionHttpSessionMiddleware::class,
AppMiddlewareAuthenticationMiddleware::class,
ApplicationFactory::DISPATCH_MIDDLEWARE,
],
'priority' => 1,
],
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
To summarise...
● PSR-7 is important
● Diactoros is just a PSR-7 implementation
● Stratigility is a middleware pipeline
● Expressive is a glue for container, router (and templating)
● DoctrineModule can be used (with some fiddling)
● container-interop-doctrine makes Doctrine work easily
● Middleware gives you good controls
Any questions?
http://joind.in/
James Titcumb @asgrim

More Related Content

What's hot (20)

PDF
Gradle in a Polyglot World
Schalk Cronjé
 
PDF
Information security programming in ruby
Hiroshi Nakamura
 
PDF
Expressive Microservice Framework Blastoff
Adam Culp
 
PDF
Foundations of Zend Framework
Adam Culp
 
PPTX
Power shell training
David Brabant
 
PPT
ZFConf 2012: Dependency Management в PHP и Zend Framework 2 (Кирилл Чебунин)
ZFConf Conference
 
PDF
Submit PHP: Standards in PHP world. Михайло Морозов
Binary Studio
 
PPTX
PSGI and Plack from first principles
Perl Careers
 
PPTX
Professional Help for PowerShell Modules
June Blender
 
ODP
Getting started with Perl XS and Inline::C
daoswald
 
KEY
Plack perl superglue for web frameworks and servers
Tatsuhiko Miyagawa
 
KEY
Intro to PSGI and Plack
Tatsuhiko Miyagawa
 
KEY
Plack at YAPC::NA 2010
Tatsuhiko Miyagawa
 
PPTX
Powershell Demo Presentation
Avik Bhattacharyya
 
PDF
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
Fernando Hamasaki de Amorim
 
PDF
How DSL works on Ruby
Hiroshi SHIBATA
 
PDF
Modern Black Mages Fighting in the Real World
SATOSHI TAGOMORI
 
PPTX
HHVM: Efficient and Scalable PHP/Hack Execution / Guilherme Ottoni (Facebook)
Ontico
 
KEY
Introducing Command Line Applications with Ruby
Nikhil Mungel
 
PDF
PECL Picks - Extensions to make your life better
ZendCon
 
Gradle in a Polyglot World
Schalk Cronjé
 
Information security programming in ruby
Hiroshi Nakamura
 
Expressive Microservice Framework Blastoff
Adam Culp
 
Foundations of Zend Framework
Adam Culp
 
Power shell training
David Brabant
 
ZFConf 2012: Dependency Management в PHP и Zend Framework 2 (Кирилл Чебунин)
ZFConf Conference
 
Submit PHP: Standards in PHP world. Михайло Морозов
Binary Studio
 
PSGI and Plack from first principles
Perl Careers
 
Professional Help for PowerShell Modules
June Blender
 
Getting started with Perl XS and Inline::C
daoswald
 
Plack perl superglue for web frameworks and servers
Tatsuhiko Miyagawa
 
Intro to PSGI and Plack
Tatsuhiko Miyagawa
 
Plack at YAPC::NA 2010
Tatsuhiko Miyagawa
 
Powershell Demo Presentation
Avik Bhattacharyya
 
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
Fernando Hamasaki de Amorim
 
How DSL works on Ruby
Hiroshi SHIBATA
 
Modern Black Mages Fighting in the Real World
SATOSHI TAGOMORI
 
HHVM: Efficient and Scalable PHP/Hack Execution / Guilherme Ottoni (Facebook)
Ontico
 
Introducing Command Line Applications with Ruby
Nikhil Mungel
 
PECL Picks - Extensions to make your life better
ZendCon
 

Similar to Kicking off with Zend Expressive and Doctrine ORM (ZendCon 2016) (20)

PDF
Kicking off with Zend Expressive and Doctrine ORM (PHP MiNDS March 2018)
James Titcumb
 
PDF
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
James Titcumb
 
PDF
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
James Titcumb
 
PDF
Kicking off with Zend Expressive and Doctrine ORM (PHP South Africa 2018)
James Titcumb
 
PDF
Symfony2 - from the trenches
Lukas Smith
 
PDF
Symfony2 from the Trenches
Jonathan Wage
 
PDF
Ростислав Михайлив "Zend Framework 3 - evolution or revolution"
Fwdays
 
PPTX
A soa approximation on symfony
Carlos Agudo Belloso
 
PPTX
A SOA approximation on symfony
Joseluis Laso
 
PDF
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Miguel Gallardo
 
PDF
Dependency management with Composer
Jason Grimes
 
PDF
Zend Framework 2 - Basic Components
Mateusz Tymek
 
PDF
Rebuilding our Foundation
Jessica Mauerhan
 
PDF
Zend Framework 1 + Doctrine 2
Ralph Schindler
 
PDF
High quality ap is with api platform
Nelson Kopliku
 
PDF
Framework agnostic application Will it fit with Symfony? - Symfony live warsa...
Dariusz Drobisz
 
PDF
Lets play with Symfony2
Noel GUILBERT
 
PDF
The Naked Bundle - Symfony Barcelona
Matthias Noback
 
PDF
The Naked Bundle - Tryout
Matthias Noback
 
PDF
Symfony2 for Midgard Developers
Henri Bergius
 
Kicking off with Zend Expressive and Doctrine ORM (PHP MiNDS March 2018)
James Titcumb
 
Kicking off with Zend Expressive and Doctrine ORM (ConFoo YVR 2017)
James Titcumb
 
Kicking off with Zend Expressive and Doctrine ORM (PHP UK 2017)
James Titcumb
 
Kicking off with Zend Expressive and Doctrine ORM (PHP South Africa 2018)
James Titcumb
 
Symfony2 - from the trenches
Lukas Smith
 
Symfony2 from the Trenches
Jonathan Wage
 
Ростислав Михайлив "Zend Framework 3 - evolution or revolution"
Fwdays
 
A soa approximation on symfony
Carlos Agudo Belloso
 
A SOA approximation on symfony
Joseluis Laso
 
Code decoupling from Symfony (and others frameworks) - PHP Conference Brasil ...
Miguel Gallardo
 
Dependency management with Composer
Jason Grimes
 
Zend Framework 2 - Basic Components
Mateusz Tymek
 
Rebuilding our Foundation
Jessica Mauerhan
 
Zend Framework 1 + Doctrine 2
Ralph Schindler
 
High quality ap is with api platform
Nelson Kopliku
 
Framework agnostic application Will it fit with Symfony? - Symfony live warsa...
Dariusz Drobisz
 
Lets play with Symfony2
Noel GUILBERT
 
The Naked Bundle - Symfony Barcelona
Matthias Noback
 
The Naked Bundle - Tryout
Matthias Noback
 
Symfony2 for Midgard Developers
Henri Bergius
 
Ad

More from James Titcumb (20)

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

Recently uploaded (20)

PDF
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
PDF
Chris Elwell Woburn, MA - Passionate About IT Innovation
Chris Elwell Woburn, MA
 
PPTX
UiPath Academic Alliance Educator Panels: Session 2 - Business Analyst Content
DianaGray10
 
PDF
SFWelly Summer 25 Release Highlights July 2025
Anna Loughnan Colquhoun
 
PDF
Exolore The Essential AI Tools in 2025.pdf
Srinivasan M
 
PPTX
AUTOMATION AND ROBOTICS IN PHARMA INDUSTRY.pptx
sameeraaabegumm
 
PDF
Presentation - Vibe Coding The Future of Tech
yanuarsinggih1
 
PPTX
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
Fwdays
 
PDF
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
PDF
HubSpot Main Hub: A Unified Growth Platform
Jaswinder Singh
 
PDF
Jak MŚP w Europie Środkowo-Wschodniej odnajdują się w świecie AI
dominikamizerska1
 
PDF
July Patch Tuesday
Ivanti
 
PDF
SWEBOK Guide and Software Services Engineering Education
Hironori Washizaki
 
PDF
Windsurf Meetup Ottawa 2025-07-12 - Planning Mode at Reliza.pdf
Pavel Shukhman
 
PDF
CIFDAQ Weekly Market Wrap for 11th July 2025
CIFDAQ
 
PDF
Timothy Rottach - Ramp up on AI Use Cases, from Vector Search to AI Agents wi...
AWS Chicago
 
PDF
Building Real-Time Digital Twins with IBM Maximo & ArcGIS Indoors
Safe Software
 
PPTX
Top iOS App Development Company in the USA for Innovative Apps
SynapseIndia
 
PDF
Newgen Beyond Frankenstein_Build vs Buy_Digital_version.pdf
darshakparmar
 
PDF
Transcript: New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
Chris Elwell Woburn, MA - Passionate About IT Innovation
Chris Elwell Woburn, MA
 
UiPath Academic Alliance Educator Panels: Session 2 - Business Analyst Content
DianaGray10
 
SFWelly Summer 25 Release Highlights July 2025
Anna Loughnan Colquhoun
 
Exolore The Essential AI Tools in 2025.pdf
Srinivasan M
 
AUTOMATION AND ROBOTICS IN PHARMA INDUSTRY.pptx
sameeraaabegumm
 
Presentation - Vibe Coding The Future of Tech
yanuarsinggih1
 
"Autonomy of LLM Agents: Current State and Future Prospects", Oles` Petriv
Fwdays
 
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
HubSpot Main Hub: A Unified Growth Platform
Jaswinder Singh
 
Jak MŚP w Europie Środkowo-Wschodniej odnajdują się w świecie AI
dominikamizerska1
 
July Patch Tuesday
Ivanti
 
SWEBOK Guide and Software Services Engineering Education
Hironori Washizaki
 
Windsurf Meetup Ottawa 2025-07-12 - Planning Mode at Reliza.pdf
Pavel Shukhman
 
CIFDAQ Weekly Market Wrap for 11th July 2025
CIFDAQ
 
Timothy Rottach - Ramp up on AI Use Cases, from Vector Search to AI Agents wi...
AWS Chicago
 
Building Real-Time Digital Twins with IBM Maximo & ArcGIS Indoors
Safe Software
 
Top iOS App Development Company in the USA for Innovative Apps
SynapseIndia
 
Newgen Beyond Frankenstein_Build vs Buy_Digital_version.pdf
darshakparmar
 
Transcript: New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 

Kicking off with Zend Expressive and Doctrine ORM (ZendCon 2016)