* initial commit
This commit is contained in:
commit
f3939bbd13
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.idea
|
||||
composer.phar
|
||||
phpunit.xml
|
||||
vendor/
|
||||
28
.travis.yml
Normal file
28
.travis.yml
Normal file
@ -0,0 +1,28 @@
|
||||
sudo: false
|
||||
|
||||
language: php
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- php: 5.5
|
||||
- php: 5.6
|
||||
env:
|
||||
- EXECUTE_CS_CHECK=true
|
||||
- php: 7
|
||||
- php: hhvm
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
|
||||
before_install:
|
||||
- composer self-update
|
||||
|
||||
install:
|
||||
- travis_retry composer install --no-interaction --ignore-platform-reqs --prefer-source --no-scripts
|
||||
|
||||
script:
|
||||
- composer test
|
||||
- if [[ $EXECUTE_CS_CHECK == 'true' ]]; then composer cs ; fi
|
||||
|
||||
notifications:
|
||||
email: true
|
||||
526
CHANGELOG.md
Normal file
526
CHANGELOG.md
Normal file
@ -0,0 +1,526 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file, in reverse chronological order by release.
|
||||
|
||||
## 1.0.2 - 2016-04-21
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#85](https://github.com/zendframework/zend-expressive-skeleton/pull/85)
|
||||
updates the Aura.Di dependency to stable 3.X versions.
|
||||
- [#88](https://github.com/zendframework/zend-expressive-skeleton/pull/88)
|
||||
modifies the installer to remove `composer.lock` from the `.gitignore` file
|
||||
during initial installation.
|
||||
- [#89](https://github.com/zendframework/zend-expressive-skeleton/pull/89)
|
||||
updates the zend-stdlib dependency to allow usage of its v3 series.
|
||||
|
||||
## 1.0.1 - 2016-03-17
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#53](https://github.com/zendframework/zend-expressive-skeleton/pull/53)
|
||||
updates the default Pimple container script such that it now caches factory
|
||||
instances for re-use.
|
||||
- [#72](https://github.com/zendframework/zend-expressive-skeleton/pull/72)
|
||||
updates the `composer.json` to remove the possibility of installing an
|
||||
Expressive RC version, updates zend-servicemanager to allow using 3.0
|
||||
versions, and updates whoops to allow either 1.1 or 2.0 versions.
|
||||
- [#80](https://github.com/zendframework/zend-expressive-skeleton/pull/80)
|
||||
updates the default ProxyManager constraints to also allow v2 versions.
|
||||
- [#81](https://github.com/zendframework/zend-expressive-skeleton/pull/81)
|
||||
fixes an issue in the installer whereby specified constraints were not being
|
||||
passed to Composer prior to dependency resolution/installation, resulting in
|
||||
stale dependencies.
|
||||
- [#78](https://github.com/zendframework/zend-expressive-skeleton/pull/78)
|
||||
updates the shipped default error templates to remove error/exception display.
|
||||
Users who really need this functionality can write their own templates; the
|
||||
project aims to deliver a "safe by default" setting.
|
||||
|
||||
## 1.0.0 - 2016-01-28
|
||||
|
||||
First stable release.
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#69](https://github.com/zendframework/zend-expressive-skeleton/pull/69)
|
||||
updates the links in templates to point to the new documentation site on
|
||||
https://zendframework.github.io/zend-expressive/ instead of rtfd.org.
|
||||
|
||||
## 1.0.0rc8 - 2016-01-21
|
||||
|
||||
Eighth release candidate.
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#66](https://github.com/zendframework/zend-expressive-skeleton/pull/66)
|
||||
adds the `'error' => true,` declaration to the `'error'` pipeline middleware
|
||||
specification.
|
||||
- [#67](https://github.com/zendframework/zend-expressive-skeleton/pull/67)
|
||||
updates the `filp/whoops` dependency for installer development to `^1.1 || ^2.0`;
|
||||
the two are compatible for our use cases, but we should prefer the latest
|
||||
that can be installed. As 2.0 requires PHP 5.5.9, but our minimum PHP version
|
||||
is 5.5.0, we must specify both.
|
||||
|
||||
## 1.0.0rc7 - 2016-01-19
|
||||
|
||||
Seventh release candidate.
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#64](https://github.com/zendframework/zend-expressive-skeleton/pull/64)
|
||||
fixes the installer script to correctly rewrite the `require-dev` section
|
||||
and ensure only the development dependencies selected, as well as base
|
||||
requirements such as PHPUnit and PHP_CodeSniffer, are installed. As such,
|
||||
the `--no-dev` flag is no longer required, and development dependencies
|
||||
such as whoops are properly installed.
|
||||
|
||||
## 1.0.0rc6 - 2016-01-19
|
||||
|
||||
Sixth release candidate.
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#56](https://github.com/zendframework/zend-expressive-skeleton/pull/56)
|
||||
updates the `composer serve` command to include the `public/index.php` script
|
||||
as an argument. This ensures that asset paths that the application could
|
||||
intercept and serve will be passed to the application (previously, the
|
||||
built-in server would treat these as 404s, and never pass them to the
|
||||
application).
|
||||
- [#57](https://github.com/zendframework/zend-expressive-skeleton/pull/57)
|
||||
updates the Apache configuration rules defined in `public/.htaccess` to omit
|
||||
several that could prevent the application from intercepting requests for
|
||||
assets.
|
||||
- [#52](https://github.com/zendframework/zend-expressive-skeleton/pull/52)
|
||||
fixes the switch statement in the `HomePageAction` class to ensure the
|
||||
template name and documentation link are accurately found.
|
||||
- [#59](https://github.com/zendframework/zend-expressive-skeleton/pull/59)
|
||||
updates the `config/container.php` implementation for zend-servicemanager such
|
||||
that it can work with either v2 or v3 of that library.
|
||||
- [#60](https://github.com/zendframework/zend-expressive-skeleton/pull/60)
|
||||
updates the zend-expressive-helpers dependency to `^2.0`, and updates the
|
||||
`config/autoload/middleware-pipeline.global.php` to follow the changes in
|
||||
middleware configuration introduced in [zend-expressive #270](https://github.com/zendframework/zend-expressive/pull/270).
|
||||
The change introduces convention-based keys for "always" (execute before
|
||||
routing), "routing" (routing, listeners that act on the route result, and
|
||||
dispatching), and "error", with reasonable priorities to ensure execution
|
||||
order.
|
||||
- [#60](https://github.com/zendframework/zend-expressive-skeleton/pull/60)
|
||||
fixes the documentation for `composer create-project` to include the
|
||||
`--no-dev` flag; this is done as composer currently installs the development
|
||||
dependencies listed before the installer script rewrites the `composer.json`
|
||||
file. Running `composer update` or `composer install` within the project
|
||||
directory after the initial installation will install the development
|
||||
dependencies.
|
||||
|
||||
## 1.0.0rc5 - 2015-12-22
|
||||
|
||||
Fifth release candidate.
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#42](https://github.com/zendframework/zend-expressive-skeleton/pull/42)
|
||||
fixes some grammatical issues in the questions presented by the installer.
|
||||
- [#45](https://github.com/zendframework/zend-expressive-skeleton/pull/45)
|
||||
fixes how JS and CSS assets are added to zend-view templates.
|
||||
- [#48](https://github.com/zendframework/zend-expressive-skeleton/pull/48)
|
||||
adds unit tests for the `OptionalPackages` class (which provides the Composer
|
||||
installer scripts).
|
||||
- [#49](https://github.com/zendframework/zend-expressive-skeleton/pull/49)
|
||||
updates the Pimple support to Pimple v3, ensuring Pimple users are using the
|
||||
latest stable release.
|
||||
|
||||
## 1.0.0rc4 - 2015-12-09
|
||||
|
||||
Fourth release candidate.
|
||||
|
||||
### Added
|
||||
|
||||
- [#34](https://github.com/zendframework/zend-expressive-skeleton/pull/34)
|
||||
updates the zend-view configuration to register a factory for
|
||||
`Zend\View\HelperPluginManager`, as well as a `view_helpers` sub-key for
|
||||
registering custom view helpers.
|
||||
- [#37](https://github.com/zendframework/zend-expressive-skeleton/pull/37)
|
||||
creates the subdirectories `src/App/` and `test/AppTest/`, moving the
|
||||
subdirectories of each under those, and updating the `composer.json`
|
||||
autoloading directives accordingly. This change will allow new projects to
|
||||
implement a "modular" structure if desired, with a subdirectory per namespace.
|
||||
- [#41](https://github.com/zendframework/zend-expressive-skeleton/pull/41) adds
|
||||
the composer script "serve", which fires up the built-in PHP webserver on port
|
||||
8080; invoke using `composer serve`.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#23](https://github.com/zendframework/zend-expressive-skeleton/pull/23)
|
||||
updates the comment for the glob statements to ensure all 4 (not just 2!)
|
||||
possible matches are detailed.
|
||||
- [#24](https://github.com/zendframework/zend-expressive-skeleton/pull/24)
|
||||
updates the `config/config.php` file to store cached configuration as a plain
|
||||
PHP file, so that it can simply `include()`; this will be faster than using
|
||||
JSON-serialized structures.
|
||||
- [#30](https://github.com/zendframework/zend-expressive-skeleton/pull/30)
|
||||
updates the Twig configuration to follow the changes made for
|
||||
[zendframework/zend-expressive-twigrenderer 0.3.0](https://github.com/zendframework/zend-expressive-twigrenderer/releases/tag/0.3.0).
|
||||
The old configuration format will still work, though users *should* update
|
||||
their configuration to the new format. The change in this patch only affects
|
||||
new installs.
|
||||
- [#33](https://github.com/zendframework/zend-expressive-skeleton/pull/33)
|
||||
updates to zendframework/zend-expressive-helpers `^1.2`.
|
||||
- [#33](https://github.com/zendframework/zend-expressive-skeleton/pull/33) adds
|
||||
configuration for auto-registering the new `Zend\Expressive\Helper\UrlHelperMiddleware`
|
||||
as pipeline middleware; this fixes an issue when using the zend-view renderer
|
||||
with the `url()` helper whereby the `UrlHelper` was being registered as a
|
||||
route result observer too late to receive the `RouteResult`.
|
||||
- [#40](https://github.com/zendframework/zend-expressive-skeleton/pull/40)
|
||||
renames the namespace for the installer to `ExpressiveInstaller`.
|
||||
|
||||
## 1.0.0rc3 - 2015-12-07
|
||||
|
||||
Third release candidate.
|
||||
|
||||
### Added
|
||||
|
||||
- [#20](https://github.com/zendframework/zend-expressive-skeleton/pull/20) adds
|
||||
the ability to specify a "minimal" install; when selected, the installer will
|
||||
install modified configuration, omit some files, and remove the default
|
||||
middleware and public assets.
|
||||
- [#27](https://github.com/zendframework/zend-expressive-skeleton/pull/27) adds
|
||||
[zendframework/zend-expressive-helpers](https://github.com/zendframework/zend-expressive-helpers)
|
||||
as a dependency, and integrates the helpers into the configuration.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#13](https://github.com/zendframework/zend-expressive-skeleton/pull/13)
|
||||
updates the installer to also remove the dependency on composer/composer
|
||||
on completion.
|
||||
- [#11](https://github.com/zendframework/zend-expressive-skeleton/pull/11)
|
||||
moves the route middleware service definitions into the routes configuration
|
||||
files.
|
||||
- [#21](https://github.com/zendframework/zend-expressive-skeleton/pull/21)
|
||||
updates `require` statements in generated configuration files to use the
|
||||
`__DIR__` constant to ensure files are located relative to the origin file.
|
||||
- [#25](https://github.com/zendframework/zend-expressive-skeleton/pull/25) and
|
||||
[#29](https://github.com/zendframework/zend-expressive-skeleton/pull/29)
|
||||
update minimum versions for each router and template implementation (final
|
||||
versions for RC3 are all at `^1.0`).
|
||||
- [#29](https://github.com/zendframework/zend-expressive-skeleton/pull/29) sets
|
||||
the zend-expressive required version to `~1.0.0@rc || ^1.0`, to ensure a
|
||||
stable version is always installed.
|
||||
|
||||
## 1.0.0rc2 - 2015-10-20
|
||||
|
||||
Second release candidate.
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Updated expressive to RC2.
|
||||
- Updated subcomponent versions in installer to `^0.2`
|
||||
|
||||
## 1.0.0rc1 - 2015-10-19
|
||||
|
||||
First release candidate.
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 0.5.3 - 2015-10-16
|
||||
|
||||
### Added
|
||||
|
||||
- [#8](https://github.com/zendframework/zend-expressive-skeleton/pull/8) adds a
|
||||
routine to the installer that recursively removes the `src/Composer/`
|
||||
directory of the skeleton, ensuring you have a clean start when creating a
|
||||
project.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 0.5.2 - 2015-10-13
|
||||
|
||||
### Added
|
||||
|
||||
- [#7](https://github.com/zendframework/zend-expressive-skeleton/pull/7) adds a
|
||||
dependency on zend-stdlib for the purposes of globbing and merging
|
||||
configuration.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 0.5.1 - 2015-10-11
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#6](https://github.com/zendframework/zend-expressive-skeleton/pull/6) updates
|
||||
the zendframework/zend-view package configuration to remove the dependency on
|
||||
zendframework/zend-i18n, as it is now handled in the standalone
|
||||
zend-expressive-zendviewrenderer package.
|
||||
|
||||
## 0.5.0 - 2015-10-10
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#3](https://github.com/zendframework/zend-expressive-skeleton/pull/3) updates
|
||||
the skeleton to use zendframework/zend-expressive 0.4.0.
|
||||
|
||||
## 0.4.0 - 2015-10-09
|
||||
|
||||
First release as zend-expressive-skeleton.
|
||||
|
||||
### Added
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 0.3.0 - 2015-09-12
|
||||
|
||||
### Added
|
||||
|
||||
- Use zend-expressive template factories.
|
||||
- Use the zend view url helper in the layout template.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 0.2.0 - 2015-09-11
|
||||
|
||||
### Added
|
||||
|
||||
- [#bbb2e60](https://github.com/xtreamwayz/expressive-composer-installer/commit/bbb2e607af23e3ae23f6a9c71eb97c3c651c0ca1) adds PHPUnit tests.
|
||||
- [#791c1c6](https://github.com/xtreamwayz/expressive-composer-installer/commit/791c1c63f324ca08d08e26375f3a356102bf2ad9) adds Whoops error handler.
|
||||
- [e1d8d7bf](https://github.com/xtreamwayz/expressive-composer-installer/commit/e1d8d7bf5d5e2f51863fa59a37d1963405743201) adds config caching in production mode.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
|
||||
## 0.1.1 - 2015-09-08
|
||||
|
||||
### Added
|
||||
|
||||
- [#b4a0923](https://github.com/xtreamwayz/expressive-composer-installer/commit/b4a092386993227f8057d7ad4e0d9762659eefb0) adds support for Pimple 3.0.x. Still needs testing!
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [#11](https://github.com/xtreamwayz/expressive-composer-installer/issues/11) fixes an issues where non stable packages are not being installed correctly.
|
||||
|
||||
## 0.1.0 - 2015-09-07
|
||||
|
||||
Initial tagged release.
|
||||
|
||||
### Added
|
||||
|
||||
- Everything.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Removed
|
||||
|
||||
- Nothing.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Nothing.
|
||||
43
CONDUCT.md
Normal file
43
CONDUCT.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
The Zend Framework project adheres to [The Code Manifesto](http://codemanifesto.com)
|
||||
as its guidelines for contributor interactions.
|
||||
|
||||
## The Code Manifesto
|
||||
|
||||
We want to work in an ecosystem that empowers developers to reach their
|
||||
potential — one that encourages growth and effective collaboration. A space that
|
||||
is safe for all.
|
||||
|
||||
A space such as this benefits everyone that participates in it. It encourages
|
||||
new developers to enter our field. It is through discussion and collaboration
|
||||
that we grow, and through growth that we improve.
|
||||
|
||||
In the effort to create such a place, we hold to these values:
|
||||
|
||||
1. **Discrimination limits us.** This includes discrimination on the basis of
|
||||
race, gender, sexual orientation, gender identity, age, nationality, technology
|
||||
and any other arbitrary exclusion of a group of people.
|
||||
2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort
|
||||
levels. Remember that, and if brought to your attention, heed it.
|
||||
3. **We are our biggest assets.** None of us were born masters of our trade.
|
||||
Each of us has been helped along the way. Return that favor, when and where
|
||||
you can.
|
||||
4. **We are resources for the future.** As an extension of #3, share what you
|
||||
know. Make yourself a resource to help those that come after you.
|
||||
5. **Respect defines us.** Treat others as you wish to be treated. Make your
|
||||
discussions, criticisms and debates from a position of respectfulness. Ask
|
||||
yourself, is it true? Is it necessary? Is it constructive? Anything less is
|
||||
unacceptable.
|
||||
6. **Reactions require grace.** Angry responses are valid, but abusive language
|
||||
and vindictive actions are toxic. When something happens that offends you,
|
||||
handle it assertively, but be respectful. Escalate reasonably, and try to
|
||||
allow the offender an opportunity to explain themselves, and possibly correct
|
||||
the issue.
|
||||
7. **Opinions are just that: opinions.** Each and every one of us, due to our
|
||||
background and upbringing, have varying opinions. The fact of the matter, is
|
||||
that is perfectly acceptable. Remember this: if you respect your own
|
||||
opinions, you should respect the opinions of others.
|
||||
8. **To err is human.** You might not intend it, but mistakes do happen and
|
||||
contribute to build experience. Tolerate honest mistakes, and don't hesitate
|
||||
to apologize if you make one yourself.
|
||||
233
CONTRIBUTING.md
Normal file
233
CONTRIBUTING.md
Normal file
@ -0,0 +1,233 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
## RESOURCES
|
||||
|
||||
If you wish to contribute to Zend Framework, please be sure to
|
||||
read/subscribe to the following resources:
|
||||
|
||||
- [Coding Standards](https://github.com/zendframework/zf2/wiki/Coding-Standards)
|
||||
- [Contributor's Guide](CONTRIBUTING.md)
|
||||
- ZF Contributor's mailing list:
|
||||
Archives: http://zend-framework-community.634137.n4.nabble.com/ZF-Contributor-f680267.html
|
||||
Subscribe: zf-contributors-subscribe@lists.zend.com
|
||||
- ZF Contributor's IRC channel:
|
||||
#zftalk.dev on Freenode.net
|
||||
|
||||
If you are working on new features or refactoring [create a proposal](https://github.com/zendframework/zend-expressive-skeleton/issues/new).
|
||||
|
||||
## Reporting Potential Security Issues
|
||||
|
||||
If you have encountered a potential security vulnerability, please **DO NOT** report it on the public
|
||||
issue tracker: send it to us at [zf-security@zend.com](mailto:zf-security@zend.com) instead.
|
||||
We will work with you to verify the vulnerability and patch it as soon as possible.
|
||||
|
||||
When reporting issues, please provide the following information:
|
||||
|
||||
- Component(s) affected
|
||||
- A description indicating how to reproduce the issue
|
||||
- A summary of the security vulnerability and impact
|
||||
|
||||
We request that you contact us via the email address above and give the project
|
||||
contributors a chance to resolve the vulnerability and issue a new release prior
|
||||
to any public exposure; this helps protect users and provides them with a chance
|
||||
to upgrade and/or update in order to protect their applications.
|
||||
|
||||
For sensitive email communications, please use [our PGP key](http://framework.zend.com/zf-security-pgp-key.asc).
|
||||
|
||||
## RUNNING TESTS
|
||||
|
||||
To run tests:
|
||||
|
||||
- Clone the repository:
|
||||
|
||||
```console
|
||||
$ git clone git@github.com:zendframework/zend-expressive-skeleton.git
|
||||
$ cd zend-expressive-skeleton
|
||||
```
|
||||
|
||||
- Install dependencies via composer:
|
||||
|
||||
```console
|
||||
$ composer install
|
||||
```
|
||||
|
||||
**NOTE:** If you are wanting to test the installer itself, add the
|
||||
`--no-scripts` flag to the `composer install` command.
|
||||
|
||||
If you don't have `curl` installed, you can also download `composer.phar` from
|
||||
https://getcomposer.org/:
|
||||
|
||||
```console
|
||||
$ curl -sS https://getcomposer.org/installer | php --
|
||||
$ ln -s composer.phar composer
|
||||
```
|
||||
|
||||
- Run the tests using the "test" command shipped in the `composer.json`:
|
||||
|
||||
```console
|
||||
$ composer test
|
||||
```
|
||||
|
||||
You can turn on conditional tests with the `phpunit.xml` file.
|
||||
To do so:
|
||||
|
||||
- Copy `phpunit.xml.dist` file to `phpunit.xml`
|
||||
- Edit `phpunit.xml` to enable any specific functionality you
|
||||
want to test, as well as to provide test values to utilize.
|
||||
|
||||
## Running Coding Standards Checks
|
||||
|
||||
First, ensure you've installed dependencies via composer, per the previous
|
||||
section on running tests.
|
||||
|
||||
To run CS checks only:
|
||||
|
||||
```console
|
||||
$ composer cs
|
||||
```
|
||||
|
||||
To attempt to automatically fix common CS issues:
|
||||
|
||||
|
||||
```console
|
||||
$ composer cs-fix
|
||||
```
|
||||
|
||||
If the above fixes any CS issues, please re-run the tests to ensure
|
||||
they pass, and make sure you add and commit the changes after verification.
|
||||
|
||||
## Recommended Workflow for Contributions
|
||||
|
||||
Your first step is to establish a public repository from which we can
|
||||
pull your work into the master repository. We recommend using
|
||||
[GitHub](https://github.com), as that is where the component is already hosted.
|
||||
|
||||
1. Setup a [GitHub account](http://github.com/), if you haven't yet
|
||||
2. Fork the repository (http://github.com/zendframework/zend-expressive-skeleton)
|
||||
3. Clone the canonical repository locally and enter it.
|
||||
|
||||
```console
|
||||
$ git clone git://github.com:zendframework/zend-expressive-skeleton.git
|
||||
$ cd zend-expressive-skeleton
|
||||
```
|
||||
|
||||
4. Add a remote to your fork; substitute your GitHub username in the command
|
||||
below.
|
||||
|
||||
```console
|
||||
$ git remote add {username} git@github.com:{username}/zend-expressive-skeleton.git
|
||||
$ git fetch {username}
|
||||
```
|
||||
|
||||
### Keeping Up-to-Date
|
||||
|
||||
Periodically, you should update your fork or personal repository to
|
||||
match the canonical ZF repository. Assuming you have setup your local repository
|
||||
per the instructions above, you can do the following:
|
||||
|
||||
|
||||
```console
|
||||
$ git checkout master
|
||||
$ git fetch origin
|
||||
$ git rebase origin/master
|
||||
# OPTIONALLY, to keep your remote up-to-date -
|
||||
$ git push {username} master:master
|
||||
```
|
||||
|
||||
If you're tracking other branches -- for example, the "develop" branch, where
|
||||
new feature development occurs -- you'll want to do the same operations for that
|
||||
branch; simply substitute "develop" for "master".
|
||||
|
||||
### Working on a patch
|
||||
|
||||
We recommend you do each new feature or bugfix in a new branch. This simplifies
|
||||
the task of code review as well as the task of merging your changes into the
|
||||
canonical repository.
|
||||
|
||||
A typical workflow will then consist of the following:
|
||||
|
||||
1. Create a new local branch based off either your master or develop branch.
|
||||
2. Switch to your new local branch. (This step can be combined with the
|
||||
previous step with the use of `git checkout -b`.)
|
||||
3. Do some work, commit, repeat as necessary.
|
||||
4. Push the local branch to your remote repository.
|
||||
5. Send a pull request.
|
||||
|
||||
The mechanics of this process are actually quite trivial. Below, we will
|
||||
create a branch for fixing an issue in the tracker.
|
||||
|
||||
```console
|
||||
$ git checkout -b hotfix/9295
|
||||
Switched to a new branch 'hotfix/9295'
|
||||
```
|
||||
|
||||
... do some work ...
|
||||
|
||||
|
||||
```console
|
||||
$ git commit
|
||||
```
|
||||
|
||||
... write your log message ...
|
||||
|
||||
|
||||
```console
|
||||
$ git push {username} hotfix/9295:hotfix/9295
|
||||
Counting objects: 38, done.
|
||||
Delta compression using up to 2 threads.
|
||||
Compression objects: 100% (18/18), done.
|
||||
Writing objects: 100% (20/20), 8.19KiB, done.
|
||||
Total 20 (delta 12), reused 0 (delta 0)
|
||||
To ssh://git@github.com/{username}/zend-expressive-skeleton.git
|
||||
b5583aa..4f51698 HEAD -> master
|
||||
```
|
||||
|
||||
To send a pull request, you have two options.
|
||||
|
||||
If using GitHub, you can do the pull request from there. Navigate to
|
||||
your repository, select the branch you just created, and then select the
|
||||
"Pull Request" button in the upper right. Select the user/organization
|
||||
"zendframework" as the recipient.
|
||||
|
||||
If using your own repository - or even if using GitHub - you can use `git
|
||||
format-patch` to create a patchset for us to apply; in fact, this is
|
||||
**recommended** for security-related patches. If you use `format-patch`, please
|
||||
send the patches as attachments to:
|
||||
|
||||
- zf-devteam@zend.com for patches without security implications
|
||||
- zf-security@zend.com for security patches
|
||||
|
||||
#### What branch to issue the pull request against?
|
||||
|
||||
Which branch should you issue a pull request against?
|
||||
|
||||
- For fixes against the stable release, issue the pull request against the
|
||||
"master" branch.
|
||||
- For new features, or fixes that introduce new elements to the public API (such
|
||||
as new public methods or properties), issue the pull request against the
|
||||
"develop" branch.
|
||||
|
||||
### Branch Cleanup
|
||||
|
||||
As you might imagine, if you are a frequent contributor, you'll start to
|
||||
get a ton of branches both locally and on your remote.
|
||||
|
||||
Once you know that your changes have been accepted to the master
|
||||
repository, we suggest doing some cleanup of these branches.
|
||||
|
||||
- Local branch cleanup
|
||||
|
||||
```console
|
||||
$ git branch -d <branchname>
|
||||
```
|
||||
|
||||
- Remote branch removal
|
||||
|
||||
```console
|
||||
$ git push {username} :<branchname>
|
||||
```
|
||||
|
||||
|
||||
## Conduct
|
||||
|
||||
Please see our [CONDUCT.md](CONDUCT.md) to understand expected behavior when interacting with others in the project.
|
||||
12
LICENSE.md
Normal file
12
LICENSE.md
Normal file
@ -0,0 +1,12 @@
|
||||
Copyright (c) 2015, Zend Technologies USA, Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
- Neither the name of Zend Technologies USA, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
88
README.md
Normal file
88
README.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Expressive Skeleton and Installer
|
||||
|
||||
[](https://secure.travis-ci.org/zendframework/zend-expressive-skeleton)
|
||||
|
||||
*Begin developing PSR-7 middleware applications in seconds!*
|
||||
|
||||
[zend-expressive](https://github.com/zendframework/zend-expressive) builds on
|
||||
[zend-stratigility](https://github.com/zendframework/zend-stratigility) to
|
||||
provide a minimalist PSR-7 middleware framework for PHP with routing, DI
|
||||
container, optional templating, and optional error handling capabilities.
|
||||
|
||||
This installer will setup a skeleton application based on zend-expressive by
|
||||
choosing optional packages based on user input as demonstrated in the following
|
||||
screenshot:
|
||||
|
||||

|
||||
|
||||
The user selected packages are saved into `composer.json` so that everyone else
|
||||
working on the project have the same packages installed. Configuration files and
|
||||
templates are prepared for first use. The installer command is removed from
|
||||
`composer.json` after setup succeeded, and all installer related files are
|
||||
removed.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Start your new Expressive project with composer:
|
||||
|
||||
```bash
|
||||
$ composer create-project zendframework/zend-expressive-skeleton <project-path>
|
||||
```
|
||||
|
||||
After choosing and installing the packages you want, go to the
|
||||
`<project-path>` and start PHP's built-in web server to verify installation:
|
||||
|
||||
```bash
|
||||
$ composer serve
|
||||
```
|
||||
|
||||
You can then browse to http://localhost:8080.
|
||||
|
||||
> ### Setting a timeout
|
||||
>
|
||||
> Composer commands time out after 300 seconds (5 minutes). On Linux-based
|
||||
> systems, the `php -S` command that `composer server` spawns continues running
|
||||
> as a background process, but on other systems halts when the timeout occurs.
|
||||
>
|
||||
> If you want the server to live longer, you can use the
|
||||
> `COMPOSER_PROCESS_TIMEOUT` environment variable when executing `composer
|
||||
> serve` to extend the timeout. As an example, the following will extend it
|
||||
> to a full day:
|
||||
>
|
||||
> ```bash
|
||||
> $ COMPOSER_PROCESS_TIMEOUT=86400 composer serve
|
||||
> ```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the installer fails during the ``composer create-project`` phase, please go
|
||||
through the following list before opening a new issue. Most issues we have seen
|
||||
so far can be solved by `self-update` and `clear-cache`.
|
||||
|
||||
1. Be sure to work with the latest version of composer by running `composer self-update`.
|
||||
2. Try clearing Composer's cache by running `composer clear-cache`.
|
||||
|
||||
If neither of the above help, you might face more serious issues:
|
||||
|
||||
- Info about the [zlib_decode error](https://github.com/composer/composer/issues/4121).
|
||||
- Info and solutions for [composer degraded mode](https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode).
|
||||
|
||||
## Skeleton Development
|
||||
|
||||
This section applies only if you cloned this repo with `git clone`, not when you
|
||||
installed expressive with `composer create-project ...`.
|
||||
|
||||
If you want to run tests against the installer, you need to clone this repo and
|
||||
setup all dependencies with composer. Make sure you **prevent composer running
|
||||
scripts** with `--no-scripts`, otherwise it will remove the installer and all
|
||||
tests.
|
||||
|
||||
```bash
|
||||
$ composer install --no-scripts
|
||||
$ composer test
|
||||
```
|
||||
|
||||
Please note that the installer tests remove installed config files and templates
|
||||
before and after running the tests.
|
||||
|
||||
Before contributing read [the contributing guide](CONTRIBUTING.md).
|
||||
9
cli-config.php
Normal file
9
cli-config.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
$container = require 'config/container.php';
|
||||
|
||||
return new \Symfony\Component\Console\Helper\HelperSet([
|
||||
'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper(
|
||||
$container->get('doctrine.entity_manager.orm_default')
|
||||
),
|
||||
]);
|
||||
58
composer.json
Normal file
58
composer.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "zendframework/zend-expressive-skeleton",
|
||||
"description": "Zend expressive skelton. Begin developing PSR-7 middleware applications in seconds",
|
||||
"type": "project",
|
||||
"homepage": "https://github.com/zendframework/zend-expressive-skeleton",
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Geert Eltink",
|
||||
"homepage": "https://xtreamwayz.com/"
|
||||
}
|
||||
],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0-dev",
|
||||
"dev-develop": "1.1-dev"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.5 || ^7.0",
|
||||
"roave/security-advisories": "dev-master",
|
||||
"zendframework/zend-expressive": "^1.0",
|
||||
"zendframework/zend-expressive-helpers": "^2.0",
|
||||
"zendframework/zend-stdlib": "^2.7 || ^3.0",
|
||||
"zendframework/zend-expressive-fastroute": "^1.0",
|
||||
"zendframework/zend-servicemanager": "^2.7.3 || ^3.0",
|
||||
"ocramius/proxy-manager": "^1.0 || ^2.0",
|
||||
"dasprid/container-interop-doctrine": "^0.2.2",
|
||||
"zendframework/zend-json": "^3.0",
|
||||
"zendframework/zend-hydrator": "^2.2",
|
||||
"zendframework/zend-filter": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8",
|
||||
"squizlabs/php_codesniffer": "^2.3",
|
||||
"filp/whoops": "^1.1 || ^2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/App/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"AppTest\\": "test/AppTest/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"check": [
|
||||
"@cs",
|
||||
"@test"
|
||||
],
|
||||
"cs": "phpcs",
|
||||
"cs-fix": "phpcbf",
|
||||
"serve": "php -S 0.0.0.0:8080 -t public/ public/index.php",
|
||||
"test": "phpunit"
|
||||
}
|
||||
}
|
||||
3128
composer.lock
generated
Normal file
3128
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
config/autoload/.gitignore
vendored
Normal file
2
config/autoload/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
local.php
|
||||
*.local.php
|
||||
26
config/autoload/dependencies.global.php
Normal file
26
config/autoload/dependencies.global.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
use Zend\Expressive\Application;
|
||||
use Zend\Expressive\Container\ApplicationFactory;
|
||||
use Zend\Expressive\Helper;
|
||||
|
||||
return [
|
||||
// Provides application-wide services.
|
||||
// We recommend using fully-qualified class names whenever possible as
|
||||
// service names.
|
||||
'dependencies' => [
|
||||
// Use 'invokables' for constructor-less services, or services that do
|
||||
// not require arguments to the constructor. Map a service name to the
|
||||
// class name.
|
||||
'invokables' => [
|
||||
// Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class,
|
||||
Helper\ServerUrlHelper::class => Helper\ServerUrlHelper::class,
|
||||
],
|
||||
// Use 'factories' for services provided by callbacks/factory classes.
|
||||
'factories' => [
|
||||
Application::class => ApplicationFactory::class,
|
||||
Helper\UrlHelper::class => Helper\UrlHelperFactory::class,
|
||||
'doctrine.entity_manager.orm_default' => \ContainerInteropDoctrine\EntityManagerFactory::class,
|
||||
'doctrine.hydrator' => \App\Hydrator\DoctrineObjectFactory::class,
|
||||
],
|
||||
],
|
||||
];
|
||||
19
config/autoload/doctrine.global.php
Normal file
19
config/autoload/doctrine.global.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'doctrine' => [
|
||||
'driver' => [
|
||||
'orm_default' => [
|
||||
'class' => \Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain::class,
|
||||
'drivers' => [
|
||||
'App\Entity' => 'my_entity',
|
||||
],
|
||||
],
|
||||
'my_entity' => [
|
||||
'class' => \Doctrine\ORM\Mapping\Driver\AnnotationDriver::class,
|
||||
'cache' => 'array',
|
||||
'paths' => __DIR__ . '/../../src/App/Entity',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
14
config/autoload/doctrine.local.dist.php
Normal file
14
config/autoload/doctrine.local.dist.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'doctrine' => [
|
||||
'connection' => [
|
||||
'orm_default' => [
|
||||
'params' => [
|
||||
'url' => 'mysqli://user:passwd@host/database',
|
||||
'charset' => 'UTF8',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
21
config/autoload/errorhandler.local.dist.php
Normal file
21
config/autoload/errorhandler.local.dist.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
'invokables' => [
|
||||
'Zend\Expressive\Whoops' => Whoops\Run::class,
|
||||
'Zend\Expressive\WhoopsPageHandler' => Whoops\Handler\PrettyPageHandler::class,
|
||||
],
|
||||
'factories' => [
|
||||
'Zend\Expressive\FinalHandler' => Zend\Expressive\Container\WhoopsErrorHandlerFactory::class,
|
||||
],
|
||||
],
|
||||
|
||||
'whoops' => [
|
||||
'json_exceptions' => [
|
||||
'display' => true,
|
||||
'show_trace' => true,
|
||||
'ajax_only' => true,
|
||||
],
|
||||
],
|
||||
];
|
||||
7
config/autoload/local.php.dist
Normal file
7
config/autoload/local.php.dist
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'debug' => true,
|
||||
|
||||
'config_cache_enabled' => false,
|
||||
];
|
||||
69
config/autoload/middleware-pipeline.global.php
Normal file
69
config/autoload/middleware-pipeline.global.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
use Zend\Expressive\Container\ApplicationFactory;
|
||||
use Zend\Expressive\Helper;
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
'factories' => [
|
||||
Helper\ServerUrlMiddleware::class => Helper\ServerUrlMiddlewareFactory::class,
|
||||
Helper\UrlHelperMiddleware::class => Helper\UrlHelperMiddlewareFactory::class,
|
||||
],
|
||||
],
|
||||
// This can be used to seed pre- and/or post-routing middleware
|
||||
'middleware_pipeline' => [
|
||||
// An array of middleware to register. Each item is of the following
|
||||
// specification:
|
||||
//
|
||||
// [
|
||||
// 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.
|
||||
// ],
|
||||
//
|
||||
// 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 file defines
|
||||
// some conventional keys for middleware to execute early, routing
|
||||
// middleware, and error middleware.
|
||||
'always' => [
|
||||
'middleware' => [
|
||||
// Add more middleware here that you want to execute on
|
||||
// every request:
|
||||
// - bootstrapping
|
||||
// - pre-conditions
|
||||
// - modifications to outgoing responses
|
||||
Helper\ServerUrlMiddleware::class,
|
||||
],
|
||||
'priority' => 10000,
|
||||
],
|
||||
|
||||
'routing' => [
|
||||
'middleware' => [
|
||||
ApplicationFactory::ROUTING_MIDDLEWARE,
|
||||
Helper\UrlHelperMiddleware::class,
|
||||
// Add more middleware here that needs to introspect the routing
|
||||
// results; this might include:
|
||||
// - route-based authentication
|
||||
// - route-based validation
|
||||
// - etc.
|
||||
ApplicationFactory::DISPATCH_MIDDLEWARE,
|
||||
],
|
||||
'priority' => 1,
|
||||
],
|
||||
|
||||
'error' => [
|
||||
'middleware' => [
|
||||
// Add error middleware here.
|
||||
],
|
||||
'error' => true,
|
||||
'priority' => -10000,
|
||||
],
|
||||
],
|
||||
];
|
||||
62
config/autoload/routes.global.php
Normal file
62
config/autoload/routes.global.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'dependencies' => [
|
||||
'invokables' => [
|
||||
Zend\Expressive\Router\RouterInterface::class => Zend\Expressive\Router\FastRouteRouter::class,
|
||||
App\Action\PingAction::class => App\Action\PingAction::class,
|
||||
],
|
||||
'factories' => [
|
||||
App\Action\HomePageAction::class => App\Action\HomePageFactory::class,
|
||||
App\Action\Article\ListAction::class => App\Action\Article\ListFactory::class,
|
||||
App\Action\Article\GetAction::class => App\Action\Article\GetFactory::class,
|
||||
App\Action\Article\PostAction::class => App\Action\Article\PostFactory::class,
|
||||
App\Action\Article\PutAction::class => App\Action\Article\PutFactory::class,
|
||||
App\Action\Article\DeleteAction::class => App\Action\Article\DeleteFactory::class,
|
||||
],
|
||||
],
|
||||
'routes' => [
|
||||
[
|
||||
'name' => 'home',
|
||||
'path' => '/',
|
||||
'middleware' => App\Action\HomePageAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'api.article.list',
|
||||
'path' => '/api/article',
|
||||
'middleware' => App\Action\Article\ListAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'api.article.get',
|
||||
'path' => '/api/article/{id:\d+}',
|
||||
'middleware' => App\Action\Article\GetAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
[
|
||||
'name' => 'api.article.add',
|
||||
'path' => '/api/article',
|
||||
'middleware' => App\Action\Article\PostAction::class,
|
||||
'allowed_methods' => ['POST'],
|
||||
],
|
||||
[
|
||||
'name' => 'api.article.update',
|
||||
'path' => '/api/article/{id:\d+}',
|
||||
'middleware' => App\Action\Article\PutAction::class,
|
||||
'allowed_methods' => ['PUT'],
|
||||
],
|
||||
[
|
||||
'name' => 'api.article.delete',
|
||||
'path' => '/api/article/{id:\d+}',
|
||||
'middleware' => App\Action\Article\DeleteAction::class,
|
||||
'allowed_methods' => ['DELETE'],
|
||||
],
|
||||
[
|
||||
'name' => 'api.ping',
|
||||
'path' => '/api/ping',
|
||||
'middleware' => App\Action\PingAction::class,
|
||||
'allowed_methods' => ['GET'],
|
||||
],
|
||||
],
|
||||
];
|
||||
14
config/autoload/zend-expressive.global.php
Normal file
14
config/autoload/zend-expressive.global.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'debug' => false,
|
||||
|
||||
'config_cache_enabled' => false,
|
||||
|
||||
'zend-expressive' => [
|
||||
'error_handler' => [
|
||||
'template_404' => 'error::404',
|
||||
'template_error' => 'error::error',
|
||||
],
|
||||
],
|
||||
];
|
||||
35
config/config.php
Normal file
35
config/config.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Zend\Stdlib\ArrayUtils;
|
||||
use Zend\Stdlib\Glob;
|
||||
|
||||
/**
|
||||
* Configuration files are loaded in a specific order. First ``global.php``, then ``*.global.php``.
|
||||
* then ``local.php`` and finally ``*.local.php``. This way local settings overwrite global settings.
|
||||
*
|
||||
* The configuration can be cached. This can be done by setting ``config_cache_enabled`` to ``true``.
|
||||
*
|
||||
* Obviously, if you use closures in your config you can't cache it.
|
||||
*/
|
||||
|
||||
$cachedConfigFile = 'data/cache/app_config.php';
|
||||
|
||||
$config = [];
|
||||
if (is_file($cachedConfigFile)) {
|
||||
// Try to load the cached config
|
||||
$config = include $cachedConfigFile;
|
||||
} 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);
|
||||
}
|
||||
|
||||
// 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) . ';');
|
||||
}
|
||||
}
|
||||
|
||||
// Return an ArrayObject so we can inject the config as a service in Aura.Di
|
||||
// and still use array checks like ``is_array``.
|
||||
return new ArrayObject($config, ArrayObject::ARRAY_AS_PROPS);
|
||||
16
config/container.php
Normal file
16
config/container.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Zend\ServiceManager\Config;
|
||||
use Zend\ServiceManager\ServiceManager;
|
||||
|
||||
// Load configuration
|
||||
$config = require __DIR__ . '/config.php';
|
||||
|
||||
// Build container
|
||||
$container = new ServiceManager();
|
||||
(new Config($config['dependencies']))->configureServiceManager($container);
|
||||
|
||||
// Inject config
|
||||
$container->setService('config', $config);
|
||||
|
||||
return $container;
|
||||
2
data/.gitignore
vendored
Normal file
2
data/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
9
nbproject/private/private.properties
Normal file
9
nbproject/private/private.properties
Normal file
@ -0,0 +1,9 @@
|
||||
auxiliary.org-netbeans-modules-php-editor.fluent_2e_setter_2e_project_2e_property=true
|
||||
auxiliary.org-netbeans-modules-php-editor.getter_2e_setter_2e_method_2e_name_2e_generation=AS_JAVA
|
||||
auxiliary.org-netbeans-modules-php-editor.public_2e_modifier_2e_project_2e_property=true
|
||||
copy.src.files=false
|
||||
copy.src.on.open=false
|
||||
copy.src.target=/var/www/simen-backend-middleware
|
||||
index.file=
|
||||
run.as=LOCAL
|
||||
url=http://localhost/simen-backend-middleware/
|
||||
7
nbproject/project.properties
Normal file
7
nbproject/project.properties
Normal file
@ -0,0 +1,7 @@
|
||||
include.path=${php.global.include.path}
|
||||
php.version=PHP_56
|
||||
source.encoding=UTF-8
|
||||
src.dir=.
|
||||
tags.asp=false
|
||||
tags.short=false
|
||||
web.root=.
|
||||
9
nbproject/project.xml
Normal file
9
nbproject/project.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://www.netbeans.org/ns/project/1">
|
||||
<type>org.netbeans.modules.php.project</type>
|
||||
<configuration>
|
||||
<data xmlns="http://www.netbeans.org/ns/php-project/1">
|
||||
<name>simen-backend-middleware</name>
|
||||
</data>
|
||||
</configuration>
|
||||
</project>
|
||||
20
phpcs.xml
Normal file
20
phpcs.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="Zend Framework coding standard">
|
||||
<description>Zend Framework coding standard</description>
|
||||
|
||||
<!-- display progress -->
|
||||
<arg value="p"/>
|
||||
<arg name="colors"/>
|
||||
|
||||
<!-- inherit rules from: -->
|
||||
<rule ref="PSR2"/>
|
||||
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
|
||||
<rule ref="Squiz.WhiteSpace.SuperfluousWhitespace">
|
||||
<properties>
|
||||
<property name="ignoreBlankLines" value="false"/>
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<!-- Paths to check -->
|
||||
<file>src</file>
|
||||
</ruleset>
|
||||
13
phpunit.xml.dist
Normal file
13
phpunit.xml.dist
Normal file
@ -0,0 +1,13 @@
|
||||
<phpunit bootstrap="./vendor/autoload.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite name="App\\Tests">
|
||||
<directory>./test</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">src</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
17
public/.htaccess
Normal file
17
public/.htaccess
Normal file
@ -0,0 +1,17 @@
|
||||
RewriteEngine On
|
||||
# The following rule tells Apache that if the requested filename
|
||||
# exists, simply serve it.
|
||||
RewriteCond %{REQUEST_FILENAME} -s [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -l [OR]
|
||||
RewriteCond %{REQUEST_FILENAME} -d
|
||||
RewriteRule ^.*$ - [NC,L]
|
||||
|
||||
# The following rewrites all other queries to index.php. The
|
||||
# condition ensures that if you are using Apache aliases to do
|
||||
# mass virtual hosting, the base path will be prepended to
|
||||
# allow proper resolution of the index.php file; it will work
|
||||
# in non-aliased environments as well, providing a safe, one-size
|
||||
# fits all solution.
|
||||
RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
|
||||
RewriteRule ^(.*) - [E=BASE:%1]
|
||||
RewriteRule ^(.*)$ %{ENV:BASE}index.php [NC,L]
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
18
public/index.php
Normal file
18
public/index.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// Delegate static file requests back to the PHP built-in webserver
|
||||
if (php_sapi_name() === 'cli-server'
|
||||
&& is_file(__DIR__ . parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chdir(dirname(__DIR__));
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
/** @var \Interop\Container\ContainerInterface $container */
|
||||
$container = require 'config/container.php';
|
||||
|
||||
/** @var \Zend\Expressive\Application $app */
|
||||
$app = $container->get(\Zend\Expressive\Application::class);
|
||||
$app->run();
|
||||
BIN
public/zf-logo.png
Normal file
BIN
public/zf-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 600 B |
27
src/App/Action/AbstractFactory.php
Normal file
27
src/App/Action/AbstractFactory.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
abstract class AbstractFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
* @return \Doctrine\ORM\EntityManager
|
||||
*/
|
||||
protected function getEntityManager(ContainerInterface $container)
|
||||
{
|
||||
return $container->get('doctrine.entity_manager.orm_default');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
* @return \App\Hydrator\DoctrineObject
|
||||
*/
|
||||
protected function getDoctrineHydrator(ContainerInterface $container)
|
||||
{
|
||||
return $container->get('doctrine.hydrator');
|
||||
}
|
||||
}
|
||||
74
src/App/Action/AbstractFormAction.php
Normal file
74
src/App/Action/AbstractFormAction.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action;
|
||||
|
||||
use Exception;
|
||||
|
||||
abstract class AbstractFormAction
|
||||
{
|
||||
|
||||
/**
|
||||
*
|
||||
* @param type $request
|
||||
* @return type
|
||||
*/
|
||||
public function getRequestData($request)
|
||||
{
|
||||
$body = $request->getParsedBody();
|
||||
|
||||
if (!empty($body)) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
return $this->parseRequestData(
|
||||
$request->getBody()->getContents(),
|
||||
$request->getHeaderLine('content-type')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param type $input
|
||||
* @param type $contentType
|
||||
* @return type
|
||||
*/
|
||||
public function parseRequestData($input, $contentType)
|
||||
{
|
||||
$contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType);
|
||||
$parser = $this->returnParserContentType($contentTypeParts[0]);
|
||||
|
||||
return $parser($input);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param type $contentType
|
||||
* @return type
|
||||
*/
|
||||
public function returnParserContentType($contentType)
|
||||
{
|
||||
if ($contentType === 'application/x-www-form-urlencoded') {
|
||||
return function ($input) {
|
||||
parse_str($input, $data);
|
||||
return $data;
|
||||
};
|
||||
} elseif ($contentType === 'application/json') {
|
||||
return function ($input) {
|
||||
$jsonDecoder = new \Zend\Json\Json();
|
||||
try {
|
||||
return $jsonDecoder->decode($input, \Zend\Json\Json::TYPE_ARRAY);
|
||||
} catch (Exception $e) {
|
||||
return new ApiProblem(400, 'Data Parsing Error.');
|
||||
}
|
||||
};
|
||||
} elseif ($contentType === 'multipart/form-data') {
|
||||
return function ($input) {
|
||||
return $input;
|
||||
};
|
||||
}
|
||||
|
||||
return function ($input) {
|
||||
return $input;
|
||||
};
|
||||
}
|
||||
}
|
||||
49
src/App/Action/Article/DeleteAction.php
Normal file
49
src/App/Action/Article/DeleteAction.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Entity\Article;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class DeleteAction
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
|
||||
{
|
||||
$id = $request->getAttribute('id');
|
||||
|
||||
if (null === ($entity = $this->em->find(Article::class, $id))) {
|
||||
$ret = new JsonResponse([
|
||||
'success' => false
|
||||
]);
|
||||
return $ret->withStatus(404);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->em->remove($entity);
|
||||
$this->em->flush();
|
||||
} catch (\Exception $ex) {
|
||||
$ret = new JsonResponse([
|
||||
'success' => false
|
||||
]);
|
||||
return $ret->withStatus(500);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
17
src/App/Action/Article/DeleteFactory.php
Normal file
17
src/App/Action/Article/DeleteFactory.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Action\AbstractFactory;
|
||||
use App\Action\Article\DeleteAction;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class DeleteFactory extends AbstractFactory
|
||||
{
|
||||
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$em = $this->getEntityManager($container);
|
||||
return new DeleteAction($em);
|
||||
}
|
||||
}
|
||||
51
src/App/Action/Article/GetAction.php
Normal file
51
src/App/Action/Article/GetAction.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Entity\Article;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Query;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class GetAction
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
|
||||
{
|
||||
$id = $request->getAttribute('id');
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
|
||||
$entity = $qb->select('a,u,c')
|
||||
->from(Article::class, 'a')
|
||||
->leftJoin('a.author', 'u')
|
||||
->leftJoin('a.comments', 'c')
|
||||
->where('a.id = :aid')
|
||||
->setParameter('aid', $id)
|
||||
->getQuery()
|
||||
->getOneOrNullResult(Query::HYDRATE_ARRAY);
|
||||
|
||||
if (null === $entity) {
|
||||
$ret = new JsonResponse([
|
||||
'success' => false
|
||||
]);
|
||||
return $ret->withStatus(404);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'result' => $entity,
|
||||
]);
|
||||
}
|
||||
}
|
||||
17
src/App/Action/Article/GetFactory.php
Normal file
17
src/App/Action/Article/GetFactory.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Action\AbstractFactory;
|
||||
use App\Action\Article\GetAction;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class GetFactory extends AbstractFactory
|
||||
{
|
||||
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$em = $this->getEntityManager($container);
|
||||
return new GetAction($em);
|
||||
}
|
||||
}
|
||||
39
src/App/Action/Article/ListAction.php
Normal file
39
src/App/Action/Article/ListAction.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Entity\Article;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class ListAction
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
|
||||
{
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$entities = $qb->select('a,u,c')
|
||||
->from(Article::class, 'a')
|
||||
->leftJoin('a.author', 'u')
|
||||
->leftJoin('a.comments', 'c')
|
||||
->getQuery()
|
||||
->getArrayResult();
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'result' => $entities,
|
||||
]);
|
||||
}
|
||||
}
|
||||
17
src/App/Action/Article/ListFactory.php
Normal file
17
src/App/Action/Article/ListFactory.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Action\AbstractFactory;
|
||||
use App\Action\Article\ListAction;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class ListFactory extends AbstractFactory
|
||||
{
|
||||
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$em = $this->getEntityManager($container);
|
||||
return new ListAction($em);
|
||||
}
|
||||
}
|
||||
45
src/App/Action/Article/PostAction.php
Normal file
45
src/App/Action/Article/PostAction.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Action\AbstractFormAction;
|
||||
use App\Entity\Article;
|
||||
use App\Hydrator\DoctrineObject;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class PostAction extends AbstractFormAction
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var DoctrineObject
|
||||
*/
|
||||
private $hydrator;
|
||||
|
||||
public function __construct(EntityManager $em, DoctrineObject $hydrator)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->hydrator = $hydrator;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
|
||||
{
|
||||
$data = $this->getRequestData($request);
|
||||
|
||||
$entity = $this->hydrator->hydrate($data, new Article());
|
||||
$this->em->persist($entity);
|
||||
$this->em->flush();
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'result' => $entity,
|
||||
]);
|
||||
}
|
||||
}
|
||||
18
src/App/Action/Article/PostFactory.php
Normal file
18
src/App/Action/Article/PostFactory.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Action\AbstractFactory;
|
||||
use App\Action\Article\PostAction;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class PostFactory extends AbstractFactory
|
||||
{
|
||||
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$em = $this->getEntityManager($container);
|
||||
$hydrator = $this->getDoctrineHydrator($container);
|
||||
return new PostAction($em, $hydrator);
|
||||
}
|
||||
}
|
||||
53
src/App/Action/Article/PutAction.php
Normal file
53
src/App/Action/Article/PutAction.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Action\AbstractFormAction;
|
||||
use App\Entity\Article;
|
||||
use App\Hydrator\DoctrineObject;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class PutAction extends AbstractFormAction
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
/**
|
||||
* @var DoctrineObject
|
||||
*/
|
||||
private $hydrator;
|
||||
|
||||
public function __construct(EntityManager $em, DoctrineObject $hydrator)
|
||||
{
|
||||
$this->em = $em;
|
||||
$this->hydrator = $hydrator;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
|
||||
{
|
||||
$id = $request->getAttribute('id', false);
|
||||
|
||||
if (null === ($entity = $this->em->find(Article::class, $id))) {
|
||||
$ret = new JsonResponse([
|
||||
'success' => false
|
||||
]);
|
||||
return $ret->withStatus(404);
|
||||
}
|
||||
|
||||
$data = $this->getRequestData($request);
|
||||
$entity = $this->hydrator->hydrate($data, $entity);
|
||||
$this->em->persist($entity);
|
||||
$this->em->flush();
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => true,
|
||||
'result' => $entity,
|
||||
]);
|
||||
}
|
||||
}
|
||||
18
src/App/Action/Article/PutFactory.php
Normal file
18
src/App/Action/Article/PutFactory.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action\Article;
|
||||
|
||||
use App\Action\AbstractFactory;
|
||||
use App\Action\Article\PutAction;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class PutFactory extends AbstractFactory
|
||||
{
|
||||
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$em = $this->getEntityManager($container);
|
||||
$hydrator = $this->getDoctrineHydrator($container);
|
||||
return new PutAction($em, $hydrator);
|
||||
}
|
||||
}
|
||||
43
src/App/Action/HomePageAction.php
Normal file
43
src/App/Action/HomePageAction.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action;
|
||||
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Query;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
|
||||
class HomePageAction
|
||||
{
|
||||
|
||||
/**
|
||||
* @var EntityManager
|
||||
*/
|
||||
private $em;
|
||||
|
||||
public function __construct(EntityManager $em)
|
||||
{
|
||||
$this->em = $em;
|
||||
}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
|
||||
{
|
||||
$qb = $this->em->createQueryBuilder();
|
||||
$user = $qb->select('u, a, ac, uc')
|
||||
->from(User::class, 'u')
|
||||
->leftJoin('u.comments', 'uc')
|
||||
->leftJoin('u.articles', 'a')
|
||||
->leftJoin('a.comments', 'ac')
|
||||
->where('u.id = :uid')
|
||||
->setParameter('uid', 1)
|
||||
->getQuery()
|
||||
->getSingleResult(Query::HYDRATE_ARRAY);
|
||||
|
||||
return new JsonResponse([
|
||||
'welcome' => 'Congratulations! You have reached our API endpoint.',
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
}
|
||||
16
src/App/Action/HomePageFactory.php
Normal file
16
src/App/Action/HomePageFactory.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action;
|
||||
|
||||
use App\Action\HomePageAction;
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class HomePageFactory
|
||||
{
|
||||
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$em = $container->get('doctrine.entity_manager.orm_default');
|
||||
return new HomePageAction($em);
|
||||
}
|
||||
}
|
||||
15
src/App/Action/PingAction.php
Normal file
15
src/App/Action/PingAction.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Action;
|
||||
|
||||
use Zend\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class PingAction
|
||||
{
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next = null)
|
||||
{
|
||||
return new JsonResponse(['ack' => time()]);
|
||||
}
|
||||
}
|
||||
180
src/App/Entity/Article.php
Normal file
180
src/App/Entity/Article.php
Normal file
@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="article")
|
||||
*/
|
||||
class Article implements JsonSerializable
|
||||
{
|
||||
use Traits\GetterSetter;
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="title", type="string", length=255)
|
||||
* @var string
|
||||
*/
|
||||
private $title = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="content", type="string", length=65535)
|
||||
* @var string
|
||||
*/
|
||||
private $content = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="articles")
|
||||
* @ORM\JoinColumn(name="author_id", referencedColumnName="id")
|
||||
* @var User
|
||||
*/
|
||||
private $author = null;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Comment", mappedBy="author")
|
||||
* @ORM\JoinColumn(name="id", referencedColumnName="author_id", nullable=false)
|
||||
* @var Comment[]
|
||||
*/
|
||||
private $comments;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="visible", type="boolean")
|
||||
* @var bool
|
||||
*/
|
||||
private $visible = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getComments()
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getVisible()
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return Article
|
||||
*/
|
||||
public function setId(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $title
|
||||
* @return Article
|
||||
*/
|
||||
public function setTitle(string $title)
|
||||
{
|
||||
$this->title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return Article
|
||||
*/
|
||||
public function setContent(string $content)
|
||||
{
|
||||
$this->content = $content;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $author
|
||||
* @return Article
|
||||
*/
|
||||
public function setAuthor(User $author)
|
||||
{
|
||||
$this->author = $author;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Comment[] $comments
|
||||
* @return Article
|
||||
*/
|
||||
public function setComments(array $comments)
|
||||
{
|
||||
$this->comments = $comments;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $visible
|
||||
* @return Article
|
||||
*/
|
||||
public function setVisible(bool $visible)
|
||||
{
|
||||
$this->visible = $visible;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'content' => $this->content,
|
||||
'author' => $this->author,
|
||||
'comments' => $this->comments,
|
||||
'visible' => $this->visible,
|
||||
];
|
||||
}
|
||||
}
|
||||
178
src/App/Entity/Comment.php
Normal file
178
src/App/Entity/Comment.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="comment")
|
||||
*/
|
||||
class Comment implements JsonSerializable
|
||||
{
|
||||
use Traits\GetterSetter;
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="content", type="string", length=65535)
|
||||
* @var string
|
||||
*/
|
||||
private $content = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="created_at", type="datetime")
|
||||
* @var DateTime
|
||||
*/
|
||||
private $createdAt = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="Article", inversedBy="comments")
|
||||
* @ORM\JoinColumn(name="article_id", referencedColumnName="id")
|
||||
* @var Article
|
||||
*/
|
||||
private $article = null;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="comments")
|
||||
* @ORM\JoinColumn(name="author_id", referencedColumnName="id")
|
||||
* @var User
|
||||
*/
|
||||
private $author = null;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="visible", type="boolean")
|
||||
* @var bool
|
||||
*/
|
||||
private $visible = true;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getContent()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateTime
|
||||
*/
|
||||
public function getCreatedAt()
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Article
|
||||
*/
|
||||
public function getArticle()
|
||||
{
|
||||
return $this->article;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getAuthor()
|
||||
{
|
||||
return $this->author;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getVisible()
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return Comment
|
||||
*/
|
||||
public function setId(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @return Comment
|
||||
*/
|
||||
public function setContent(string $content)
|
||||
{
|
||||
$this->content = $content;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTime $createdAt
|
||||
* @return Comment
|
||||
*/
|
||||
public function setCreatedAt(DateTime $createdAt)
|
||||
{
|
||||
$this->createdAt = clone $createdAt;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Article $article
|
||||
* @return Comment
|
||||
*/
|
||||
public function setArticle(Article $article)
|
||||
{
|
||||
$this->article = $article;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $author
|
||||
* @return Comment
|
||||
*/
|
||||
public function setAuthor(User $author)
|
||||
{
|
||||
$this->author = $author;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $visible
|
||||
* @return Comment
|
||||
*/
|
||||
public function setVisible(bool $visible)
|
||||
{
|
||||
$this->visible = $visible;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'content' => $this->content,
|
||||
'createdAt' => $this->createdAt,
|
||||
'author' => $this->author,
|
||||
'article' => $this->article,
|
||||
'visible' => $this->visible,
|
||||
];
|
||||
}
|
||||
}
|
||||
52
src/App/Entity/Traits/GetterSetter.php
Normal file
52
src/App/Entity/Traits/GetterSetter.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity\Traits;
|
||||
|
||||
Trait GetterSetter
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns the getter name for a field
|
||||
*
|
||||
* @param string $field
|
||||
* @return string
|
||||
*/
|
||||
protected function getterName($field)
|
||||
{
|
||||
return sprintf('get%s', ucfirst(
|
||||
str_replace(' ', '', ucwords(str_replace('_', ' ', $field)))
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the setter name for a field
|
||||
*
|
||||
* @param string $field
|
||||
* @return string
|
||||
*/
|
||||
protected function setterName($field)
|
||||
{
|
||||
return sprintf('set%s', ucfirst(
|
||||
str_replace(' ', '', ucwords(str_replace('_', ' ', $field)))
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate entity with the given data.
|
||||
* The set* method will be used to set the data.
|
||||
*
|
||||
* @param array $data
|
||||
* @return boolean
|
||||
*/
|
||||
public function populate(array $data = [])
|
||||
{
|
||||
foreach ($data as $field => $value) {
|
||||
$setter = $this->setterName($field);
|
||||
if (method_exists($this, $setter)) {
|
||||
$this->{$setter}($value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
184
src/App/Entity/User.php
Normal file
184
src/App/Entity/User.php
Normal file
@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="user")
|
||||
*/
|
||||
class User implements JsonSerializable
|
||||
{
|
||||
use Traits\GetterSetter;
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(name="id", type="integer")
|
||||
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||
* @var int
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="name", type="string", length=150)
|
||||
* @var string
|
||||
*/
|
||||
private $name;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="email", type="string", length=255)
|
||||
* @var string
|
||||
*/
|
||||
private $email;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Article", mappedBy="author")
|
||||
* @ORM\JoinColumn(name="id", referencedColumnName="author_id", nullable=false)
|
||||
* @var Article[]
|
||||
*/
|
||||
private $articles;
|
||||
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Comment", mappedBy="author")
|
||||
* @ORM\JoinColumn(name="id", referencedColumnName="author_id", nullable=false)
|
||||
* @var Comment[]
|
||||
*/
|
||||
private $comments;
|
||||
|
||||
/**
|
||||
* @ORM\Column(name="active", type="boolean")
|
||||
* @var bool
|
||||
*/
|
||||
private $active = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->articles = new ArrayCollection();
|
||||
$this->comments = new ArrayCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getEmail()
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Article[]
|
||||
*/
|
||||
public function getArticles()
|
||||
{
|
||||
return $this->articles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Comment[]
|
||||
*/
|
||||
public function getComments()
|
||||
{
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getActive()
|
||||
{
|
||||
return $this->active;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return User
|
||||
*/
|
||||
public function setId(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @return User
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $email
|
||||
* @return User
|
||||
*/
|
||||
public function setEmail(string $email)
|
||||
{
|
||||
$this->email = $email;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Article[] $articles
|
||||
* @return User
|
||||
*/
|
||||
public function setArticles(array $articles)
|
||||
{
|
||||
$this->articles = $articles;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Comment[] $comments
|
||||
* @return User
|
||||
*/
|
||||
public function setComments(array $comments)
|
||||
{
|
||||
$this->comments = $comments;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $active
|
||||
* @return User
|
||||
*/
|
||||
public function setActive(bool $active)
|
||||
{
|
||||
$this->active = $active;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'articles' => $this->articles,
|
||||
'comments' => $this->comments,
|
||||
'active' => $this->active,
|
||||
];
|
||||
}
|
||||
}
|
||||
595
src/App/Hydrator/DoctrineObject.php
Normal file
595
src/App/Hydrator/DoctrineObject.php
Normal file
@ -0,0 +1,595 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace App\Hydrator;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Doctrine\Common\Util\Inflector;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Traversable;
|
||||
use Zend\Stdlib\ArrayUtils;
|
||||
use Zend\Hydrator\AbstractHydrator;
|
||||
use Zend\Hydrator\Filter\FilterProviderInterface;
|
||||
|
||||
/**
|
||||
* This hydrator has been completely refactored for DoctrineModule 0.7.0. It provides an easy and powerful way
|
||||
* of extracting/hydrator objects in Doctrine, by handling most associations types.
|
||||
*
|
||||
* Starting from DoctrineModule 0.8.0, the hydrator can be used multiple times with different objects
|
||||
*
|
||||
* @license MIT
|
||||
* @link http://www.doctrine-project.org/
|
||||
* @since 0.7.0
|
||||
* @author Michael Gallego <mic.gallego@gmail.com>
|
||||
*/
|
||||
class DoctrineObject extends AbstractHydrator
|
||||
{
|
||||
/**
|
||||
* @var ObjectManager
|
||||
*/
|
||||
protected $objectManager;
|
||||
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
*/
|
||||
protected $metadata;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $byValue = true;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ObjectManager $objectManager The ObjectManager to use
|
||||
* @param bool $byValue If set to true, hydrator will always use entity's public API
|
||||
*/
|
||||
public function __construct(ObjectManager $objectManager, $byValue = true)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->objectManager = $objectManager;
|
||||
$this->byValue = (bool) $byValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract values from an object
|
||||
*
|
||||
* @param object $object
|
||||
* @return array
|
||||
*/
|
||||
public function extract($object)
|
||||
{
|
||||
$this->prepare($object);
|
||||
|
||||
if ($this->byValue) {
|
||||
return $this->extractByValue($object);
|
||||
}
|
||||
|
||||
return $this->extractByReference($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate $object with the provided $data.
|
||||
*
|
||||
* @param array $data
|
||||
* @param object $object
|
||||
* @return object
|
||||
*/
|
||||
public function hydrate(array $data, $object)
|
||||
{
|
||||
$this->prepare($object);
|
||||
|
||||
if ($this->byValue) {
|
||||
return $this->hydrateByValue($data, $object);
|
||||
}
|
||||
|
||||
return $this->hydrateByReference($data, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the hydrator by adding strategies to every collection valued associations
|
||||
*
|
||||
* @param object $object
|
||||
* @return void
|
||||
*/
|
||||
protected function prepare($object)
|
||||
{
|
||||
$this->metadata = $this->objectManager->getClassMetadata(get_class($object));
|
||||
$this->prepareStrategies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare strategies before the hydrator is used
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareStrategies()
|
||||
{
|
||||
$associations = $this->metadata->getAssociationNames();
|
||||
|
||||
foreach ($associations as $association) {
|
||||
if ($this->metadata->isCollectionValuedAssociation($association)) {
|
||||
// Add a strategy if the association has none set by user
|
||||
if (!$this->hasStrategy($association)) {
|
||||
if ($this->byValue) {
|
||||
$this->addStrategy($association, new Strategy\AllowRemoveByValue());
|
||||
} else {
|
||||
$this->addStrategy($association, new Strategy\AllowRemoveByReference());
|
||||
}
|
||||
}
|
||||
|
||||
$strategy = $this->getStrategy($association);
|
||||
|
||||
if (!$strategy instanceof Strategy\AbstractCollectionStrategy) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Strategies used for collections valued associations must inherit from '
|
||||
. 'Strategy\AbstractCollectionStrategy, %s given',
|
||||
get_class($strategy)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$strategy->setCollectionName($association)
|
||||
->setClassMetadata($this->metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract values from an object using a by-value logic (this means that it uses the entity
|
||||
* API, in this case, getters)
|
||||
*
|
||||
* @param object $object
|
||||
* @throws RuntimeException
|
||||
* @return array
|
||||
*/
|
||||
protected function extractByValue($object)
|
||||
{
|
||||
$fieldNames = array_merge($this->metadata->getFieldNames(), $this->metadata->getAssociationNames());
|
||||
$methods = get_class_methods($object);
|
||||
$filter = $object instanceof FilterProviderInterface
|
||||
? $object->getFilter()
|
||||
: $this->filterComposite;
|
||||
|
||||
$data = array();
|
||||
foreach ($fieldNames as $fieldName) {
|
||||
if ($filter && !$filter->filter($fieldName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$getter = 'get' . Inflector::classify($fieldName);
|
||||
$isser = 'is' . Inflector::classify($fieldName);
|
||||
|
||||
$dataFieldName = $this->computeExtractFieldName($fieldName);
|
||||
if (in_array($getter, $methods)) {
|
||||
$data[$dataFieldName] = $this->extractValue($fieldName, $object->$getter(), $object);
|
||||
} elseif (in_array($isser, $methods)) {
|
||||
$data[$dataFieldName] = $this->extractValue($fieldName, $object->$isser(), $object);
|
||||
} elseif (substr($fieldName, 0, 2) === 'is'
|
||||
&& ctype_upper(substr($fieldName, 2, 1))
|
||||
&& in_array($fieldName, $methods)) {
|
||||
$data[$dataFieldName] = $this->extractValue($fieldName, $object->$fieldName(), $object);
|
||||
}
|
||||
|
||||
// Unknown fields are ignored
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract values from an object using a by-reference logic (this means that values are
|
||||
* directly fetched without using the public API of the entity, in this case, getters)
|
||||
*
|
||||
* @param object $object
|
||||
* @return array
|
||||
*/
|
||||
protected function extractByReference($object)
|
||||
{
|
||||
$fieldNames = array_merge($this->metadata->getFieldNames(), $this->metadata->getAssociationNames());
|
||||
$refl = $this->metadata->getReflectionClass();
|
||||
$filter = $object instanceof FilterProviderInterface
|
||||
? $object->getFilter()
|
||||
: $this->filterComposite;
|
||||
|
||||
$data = array();
|
||||
foreach ($fieldNames as $fieldName) {
|
||||
if ($filter && !$filter->filter($fieldName)) {
|
||||
continue;
|
||||
}
|
||||
$reflProperty = $refl->getProperty($fieldName);
|
||||
$reflProperty->setAccessible(true);
|
||||
|
||||
$dataFieldName = $this->computeExtractFieldName($fieldName);
|
||||
$data[$dataFieldName] = $this->extractValue($fieldName, $reflProperty->getValue($object), $object);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the object using a by-value logic (this means that it uses the entity API, in this
|
||||
* case, setters)
|
||||
*
|
||||
* @param array $data
|
||||
* @param object $object
|
||||
* @throws RuntimeException
|
||||
* @return object
|
||||
*/
|
||||
protected function hydrateByValue(array $data, $object)
|
||||
{
|
||||
$tryObject = $this->tryConvertArrayToObject($data, $object);
|
||||
$metadata = $this->metadata;
|
||||
|
||||
if (is_object($tryObject)) {
|
||||
$object = $tryObject;
|
||||
}
|
||||
|
||||
foreach ($data as $field => $value) {
|
||||
$field = $this->computeHydrateFieldName($field);
|
||||
$value = $this->handleTypeConversions($value, $metadata->getTypeOfField($field));
|
||||
$setter = 'set' . Inflector::classify($field);
|
||||
|
||||
if ($metadata->hasAssociation($field)) {
|
||||
$target = $metadata->getAssociationTargetClass($field);
|
||||
|
||||
if ($metadata->isSingleValuedAssociation($field)) {
|
||||
if (! method_exists($object, $setter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->toOne($target, $this->hydrateValue($field, $value, $data));
|
||||
|
||||
if (null === $value
|
||||
&& !current($metadata->getReflectionClass()->getMethod($setter)->getParameters())->allowsNull()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$object->$setter($value);
|
||||
} elseif ($metadata->isCollectionValuedAssociation($field)) {
|
||||
$this->toMany($object, $field, $target, $value);
|
||||
}
|
||||
} else {
|
||||
if (! method_exists($object, $setter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$object->$setter($this->hydrateValue($field, $value, $data));
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hydrate the object using a by-reference logic (this means that values are modified directly without
|
||||
* using the public API, in this case setters, and hence override any logic that could be done in those
|
||||
* setters)
|
||||
*
|
||||
* @param array $data
|
||||
* @param object $object
|
||||
* @return object
|
||||
*/
|
||||
protected function hydrateByReference(array $data, $object)
|
||||
{
|
||||
$tryObject = $this->tryConvertArrayToObject($data, $object);
|
||||
$metadata = $this->metadata;
|
||||
$refl = $metadata->getReflectionClass();
|
||||
|
||||
if (is_object($tryObject)) {
|
||||
$object = $tryObject;
|
||||
}
|
||||
|
||||
foreach ($data as $field => $value) {
|
||||
$field = $this->computeHydrateFieldName($field);
|
||||
|
||||
// Ignore unknown fields
|
||||
if (!$refl->hasProperty($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->handleTypeConversions($value, $metadata->getTypeOfField($field));
|
||||
$reflProperty = $refl->getProperty($field);
|
||||
$reflProperty->setAccessible(true);
|
||||
|
||||
if ($metadata->hasAssociation($field)) {
|
||||
$target = $metadata->getAssociationTargetClass($field);
|
||||
|
||||
if ($metadata->isSingleValuedAssociation($field)) {
|
||||
$value = $this->toOne($target, $this->hydrateValue($field, $value, $data));
|
||||
$reflProperty->setValue($object, $value);
|
||||
} elseif ($metadata->isCollectionValuedAssociation($field)) {
|
||||
$this->toMany($object, $field, $target, $value);
|
||||
}
|
||||
} else {
|
||||
$reflProperty->setValue($object, $this->hydrateValue($field, $value, $data));
|
||||
}
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function tries, given an array of data, to convert it to an object if the given array contains
|
||||
* an identifier for the object. This is useful in a context of updating existing entities, without ugly
|
||||
* tricks like setting manually the existing id directly into the entity
|
||||
*
|
||||
* @param array $data The data that may contain identifiers keys
|
||||
* @param object $object
|
||||
* @return object
|
||||
*/
|
||||
protected function tryConvertArrayToObject($data, $object)
|
||||
{
|
||||
$metadata = $this->metadata;
|
||||
$identifierNames = $metadata->getIdentifierFieldNames($object);
|
||||
$identifierValues = array();
|
||||
|
||||
if (empty($identifierNames)) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
foreach ($identifierNames as $identifierName) {
|
||||
if (!isset($data[$identifierName])) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
$identifierValues[$identifierName] = $data[$identifierName];
|
||||
}
|
||||
|
||||
return $this->find($identifierValues, $metadata->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ToOne associations
|
||||
*
|
||||
* When $value is an array but is not the $target's identifiers, $value is
|
||||
* most likely an array of fieldset data. The identifiers will be determined
|
||||
* and a target instance will be initialized and then hydrated. The hydrated
|
||||
* target will be returned.
|
||||
*
|
||||
* @param string $target
|
||||
* @param mixed $value
|
||||
* @return object
|
||||
*/
|
||||
protected function toOne($target, $value)
|
||||
{
|
||||
$metadata = $this->objectManager->getClassMetadata($target);
|
||||
|
||||
if (is_array($value) && array_keys($value) != $metadata->getIdentifier()) {
|
||||
// $value is most likely an array of fieldset data
|
||||
$identifiers = array_intersect_key(
|
||||
$value,
|
||||
array_flip($metadata->getIdentifier())
|
||||
);
|
||||
$object = $this->find($identifiers, $target) ?: new $target;
|
||||
return $this->hydrate($value, $object);
|
||||
}
|
||||
|
||||
return $this->find($value, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ToMany associations. In proper Doctrine design, Collections should not be swapped, so
|
||||
* collections are always handled by reference. Internally, every collection is handled using specials
|
||||
* strategies that inherit from AbstractCollectionStrategy class, and that add or remove elements but without
|
||||
* changing the collection of the object
|
||||
*
|
||||
* @param object $object
|
||||
* @param mixed $collectionName
|
||||
* @param string $target
|
||||
* @param mixed $values
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function toMany($object, $collectionName, $target, $values)
|
||||
{
|
||||
$metadata = $this->objectManager->getClassMetadata(ltrim($target, '\\'));
|
||||
$identifier = $metadata->getIdentifier();
|
||||
|
||||
if (!is_array($values) && !$values instanceof Traversable) {
|
||||
$values = (array)$values;
|
||||
}
|
||||
|
||||
$collection = array();
|
||||
|
||||
// If the collection contains identifiers, fetch the objects from database
|
||||
foreach ($values as $value) {
|
||||
|
||||
if ($value instanceof $target) {
|
||||
// assumes modifications have already taken place in object
|
||||
$collection[] = $value;
|
||||
continue;
|
||||
} elseif (empty($value)) {
|
||||
// assumes no id and retrieves new $target
|
||||
$collection[] = $this->find($value, $target);
|
||||
continue;
|
||||
}
|
||||
|
||||
$find = array();
|
||||
if (is_array($identifier)) {
|
||||
foreach ($identifier as $field) {
|
||||
switch (gettype($value)) {
|
||||
case 'object':
|
||||
$getter = 'get' . ucfirst($field);
|
||||
if (method_exists($value, $getter)) {
|
||||
$find[$field] = $value->$getter();
|
||||
} elseif (property_exists($value, $field)) {
|
||||
$find[$field] = $value->$field;
|
||||
}
|
||||
break;
|
||||
case 'array':
|
||||
if (array_key_exists($field, $value) && $value[$field] != null) {
|
||||
$find[$field] = $value[$field];
|
||||
unset($value[$field]); // removed identifier from persistable data
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$find[$field] = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($find) && $found = $this->find($find, $target)) {
|
||||
$collection[] = (is_array($value)) ? $this->hydrate($value, $found) : $found;
|
||||
} else {
|
||||
$collection[] = (is_array($value)) ? $this->hydrate($value, new $target) : new $target;
|
||||
}
|
||||
}
|
||||
|
||||
$collection = array_filter(
|
||||
$collection,
|
||||
function ($item) {
|
||||
return null !== $item;
|
||||
}
|
||||
);
|
||||
|
||||
// Set the object so that the strategy can extract the Collection from it
|
||||
|
||||
/** @var \DoctrineModule\Stdlib\Hydrator\Strategy\AbstractCollectionStrategy $collectionStrategy */
|
||||
$collectionStrategy = $this->getStrategy($collectionName);
|
||||
$collectionStrategy->setObject($object);
|
||||
|
||||
// We could directly call hydrate method from the strategy, but if people want to override
|
||||
// hydrateValue function, they can do it and do their own stuff
|
||||
$this->hydrateValue($collectionName, $collection, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle various type conversions that should be supported natively by Doctrine (like DateTime)
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param string $typeOfField
|
||||
* @return DateTime
|
||||
*/
|
||||
protected function handleTypeConversions($value, $typeOfField)
|
||||
{
|
||||
switch ($typeOfField) {
|
||||
case 'datetimetz':
|
||||
case 'datetime':
|
||||
case 'time':
|
||||
case 'date':
|
||||
if ('' === $value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
$dateTime = new DateTime();
|
||||
$dateTime->setTimestamp($value);
|
||||
$value = $dateTime;
|
||||
} elseif (is_string($value)) {
|
||||
$value = new DateTime($value);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an object by a given target class and identifier
|
||||
*
|
||||
* @param mixed $identifiers
|
||||
* @param string $targetClass
|
||||
*
|
||||
* @return object|null
|
||||
*/
|
||||
protected function find($identifiers, $targetClass)
|
||||
{
|
||||
if ($identifiers instanceof $targetClass) {
|
||||
return $identifiers;
|
||||
}
|
||||
|
||||
if ($this->isNullIdentifier($identifiers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->objectManager->find($targetClass, $identifiers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if a provided identifier is to be considered null
|
||||
*
|
||||
* @param mixed $identifier
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isNullIdentifier($identifier)
|
||||
{
|
||||
if (null === $identifier) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($identifier instanceof Traversable || is_array($identifier)) {
|
||||
$nonNullIdentifiers = array_filter(
|
||||
ArrayUtils::iteratorToArray($identifier),
|
||||
function ($value) {
|
||||
return null !== $value;
|
||||
}
|
||||
);
|
||||
|
||||
return empty($nonNullIdentifiers);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the naming strategy if there is one set
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function computeHydrateFieldName($field)
|
||||
{
|
||||
if ($this->hasNamingStrategy()) {
|
||||
$field = $this->getNamingStrategy()->hydrate($field);
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the naming strategy if there is one set
|
||||
*
|
||||
* @param string $field
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function computeExtractFieldName($field)
|
||||
{
|
||||
if ($this->hasNamingStrategy()) {
|
||||
$field = $this->getNamingStrategy()->extract($field);
|
||||
}
|
||||
return $field;
|
||||
}
|
||||
}
|
||||
15
src/App/Hydrator/DoctrineObjectFactory.php
Normal file
15
src/App/Hydrator/DoctrineObjectFactory.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Hydrator;
|
||||
|
||||
use Interop\Container\ContainerInterface;
|
||||
|
||||
class DoctrineObjectFactory
|
||||
{
|
||||
|
||||
public function __invoke(ContainerInterface $container)
|
||||
{
|
||||
$em = $container->get('doctrine.entity_manager.orm_default');
|
||||
return new DoctrineObject($em);
|
||||
}
|
||||
}
|
||||
66
src/App/Hydrator/Filter/PropertyName.php
Normal file
66
src/App/Hydrator/Filter/PropertyName.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace App\Hydrator\Filter;
|
||||
|
||||
use Zend\Hydrator\Filter\FilterInterface;
|
||||
|
||||
/**
|
||||
* Provides a filter to restrict returned fields by whitelisting or
|
||||
* blacklisting property names.
|
||||
*
|
||||
* @license MIT
|
||||
* @link http://www.doctrine-project.org/
|
||||
* @author Liam O'Boyle <liam@ontheroad.net.nz>
|
||||
*/
|
||||
class PropertyName implements FilterInterface
|
||||
{
|
||||
/**
|
||||
* The propteries to exclude.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $properties = array();
|
||||
|
||||
/**
|
||||
* Either an exclude or an include.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $exclude = null;
|
||||
|
||||
/**
|
||||
* @param [ string | array ] $properties The properties to exclude or include.
|
||||
* @param bool $exclude If the method should be excluded
|
||||
*/
|
||||
public function __construct($properties, $exclude = true)
|
||||
{
|
||||
$this->exclude = $exclude;
|
||||
$this->properties = is_array($properties)
|
||||
? $properties
|
||||
: array($properties);
|
||||
}
|
||||
|
||||
public function filter($property)
|
||||
{
|
||||
return in_array($property, $this->properties)
|
||||
? !$this->exclude
|
||||
: $this->exclude;
|
||||
}
|
||||
}
|
||||
190
src/App/Hydrator/Strategy/AbstractCollectionStrategy.php
Normal file
190
src/App/Hydrator/Strategy/AbstractCollectionStrategy.php
Normal file
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace App\Hydrator\Strategy;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use Zend\Hydrator\Strategy\StrategyInterface;
|
||||
|
||||
/**
|
||||
* @license MIT
|
||||
* @link http://www.doctrine-project.org/
|
||||
* @since 0.7.0
|
||||
* @author Michael Gallego <mic.gallego@gmail.com>
|
||||
*/
|
||||
abstract class AbstractCollectionStrategy implements StrategyInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $collectionName;
|
||||
|
||||
/**
|
||||
* @var ClassMetadata
|
||||
*/
|
||||
protected $metadata;
|
||||
|
||||
/**
|
||||
* @var object
|
||||
*/
|
||||
protected $object;
|
||||
|
||||
|
||||
/**
|
||||
* Set the name of the collection
|
||||
*
|
||||
* @param string $collectionName
|
||||
* @return AbstractCollectionStrategy
|
||||
*/
|
||||
public function setCollectionName($collectionName)
|
||||
{
|
||||
$this->collectionName = (string) $collectionName;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the collection
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCollectionName()
|
||||
{
|
||||
return $this->collectionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the class metadata
|
||||
*
|
||||
* @param ClassMetadata $classMetadata
|
||||
* @return AbstractCollectionStrategy
|
||||
*/
|
||||
public function setClassMetadata(ClassMetadata $classMetadata)
|
||||
{
|
||||
$this->metadata = $classMetadata;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class metadata
|
||||
*
|
||||
* @return ClassMetadata
|
||||
*/
|
||||
public function getClassMetadata()
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the object
|
||||
*
|
||||
* @param object $object
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return AbstractCollectionStrategy
|
||||
*/
|
||||
public function setObject($object)
|
||||
{
|
||||
if (!is_object($object)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf('The parameter given to setObject method of %s class is not an object', get_called_class())
|
||||
);
|
||||
}
|
||||
|
||||
$this->object = $object;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getObject()
|
||||
{
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function extract($value)
|
||||
{
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the collection by value (using the public API)
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function getCollectionFromObjectByValue()
|
||||
{
|
||||
$object = $this->getObject();
|
||||
$getter = 'get' . ucfirst($this->getCollectionName());
|
||||
|
||||
if (!method_exists($object, $getter)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'The getter %s to access collection %s in object %s does not exist',
|
||||
$getter,
|
||||
$this->getCollectionName(),
|
||||
get_class($object)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $object->$getter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the collection by reference (not using the public API)
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
protected function getCollectionFromObjectByReference()
|
||||
{
|
||||
$object = $this->getObject();
|
||||
$refl = $this->getClassMetadata()->getReflectionClass();
|
||||
$reflProperty = $refl->getProperty($this->getCollectionName());
|
||||
|
||||
$reflProperty->setAccessible(true);
|
||||
|
||||
return $reflProperty->getValue($object);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is used internally by array_udiff to check if two objects are equal, according to their
|
||||
* SPL hash. This is needed because the native array_diff only compare strings
|
||||
*
|
||||
* @param object $a
|
||||
* @param object $b
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function compareObjects($a, $b)
|
||||
{
|
||||
return strcmp(spl_object_hash($a), spl_object_hash($b));
|
||||
}
|
||||
}
|
||||
58
src/App/Hydrator/Strategy/AllowRemoveByReference.php
Normal file
58
src/App/Hydrator/Strategy/AllowRemoveByReference.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace App\Hydrator\Strategy;
|
||||
|
||||
/**
|
||||
* When this strategy is used for Collections, if the new collection does not contain elements that are present in
|
||||
* the original collection, then this strategy remove elements from the original collection. For instance, if the
|
||||
* collection initially contains elements A and B, and that the new collection contains elements B and C, then the
|
||||
* final collection will contain elements B and C (while element A will be asked to be removed).
|
||||
*
|
||||
* This strategy is by reference, this means it won't use public API to add/remove elements to the collection
|
||||
*
|
||||
* @license MIT
|
||||
* @link http://www.doctrine-project.org/
|
||||
* @since 0.7.0
|
||||
* @author Michael Gallego <mic.gallego@gmail.com>
|
||||
*/
|
||||
class AllowRemoveByReference extends AbstractCollectionStrategy
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hydrate($value)
|
||||
{
|
||||
$collection = $this->getCollectionFromObjectByReference();
|
||||
$collectionArray = $collection->toArray();
|
||||
|
||||
$toAdd = array_udiff($value, $collectionArray, array($this, 'compareObjects'));
|
||||
$toRemove = array_udiff($collectionArray, $value, array($this, 'compareObjects'));
|
||||
|
||||
foreach ($toAdd as $element) {
|
||||
$collection->add($element);
|
||||
}
|
||||
|
||||
foreach ($toRemove as $element) {
|
||||
$collection->removeElement($element);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
76
src/App/Hydrator/Strategy/AllowRemoveByValue.php
Normal file
76
src/App/Hydrator/Strategy/AllowRemoveByValue.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace App\Hydrator\Strategy;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use LogicException;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* When this strategy is used for Collections, if the new collection does not contain elements that are present in
|
||||
* the original collection, then this strategy remove elements from the original collection. For instance, if the
|
||||
* collection initially contains elements A and B, and that the new collection contains elements B and C, then the
|
||||
* final collection will contain elements B and C (while element A will be asked to be removed).
|
||||
*
|
||||
* This strategy is by value, this means it will use the public API (in this case, adder and remover)
|
||||
*
|
||||
* @license MIT
|
||||
* @link http://www.doctrine-project.org/
|
||||
* @since 0.7.0
|
||||
* @author Michael Gallego <mic.gallego@gmail.com>
|
||||
*/
|
||||
class AllowRemoveByValue extends AbstractCollectionStrategy
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hydrate($value)
|
||||
{
|
||||
// AllowRemove strategy need "adder" and "remover"
|
||||
$adder = 'add' . ucfirst($this->collectionName);
|
||||
$remover = 'remove' . ucfirst($this->collectionName);
|
||||
|
||||
if (!method_exists($this->object, $adder) || !method_exists($this->object, $remover)) {
|
||||
throw new LogicException(
|
||||
sprintf(
|
||||
'AllowRemove strategy for DoctrineModule hydrator requires both %s and %s to be defined in %s
|
||||
entity domain code, but one or both seem to be missing',
|
||||
$adder,
|
||||
$remover,
|
||||
get_class($this->object)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$collection = $this->getCollectionFromObjectByValue();
|
||||
|
||||
if ($collection instanceof Collection) {
|
||||
$collection = $collection->toArray();
|
||||
}
|
||||
|
||||
$toAdd = new ArrayCollection(array_udiff($value, $collection, array($this, 'compareObjects')));
|
||||
$toRemove = new ArrayCollection(array_udiff($collection, $value, array($this, 'compareObjects')));
|
||||
|
||||
$this->object->$adder($toAdd);
|
||||
$this->object->$remover($toRemove);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
53
src/App/Hydrator/Strategy/DisallowRemoveByReference.php
Normal file
53
src/App/Hydrator/Strategy/DisallowRemoveByReference.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace App\Hydrator\Strategy;
|
||||
|
||||
/**
|
||||
* When this strategy is used for Collections, if the new collection does not contain elements that are present in
|
||||
* the original collection, then this strategy will not remove those elements. At most, it will add new elements. For
|
||||
* instance, if the collection initially contains elements A and B, and that the new collection contains elements B
|
||||
* and C, then the final collection will contain elements A, B and C.
|
||||
*
|
||||
* This strategy is by reference, this means it won't use the public API to remove elements
|
||||
*
|
||||
* @license MIT
|
||||
* @link http://www.doctrine-project.org/
|
||||
* @since 0.7.0
|
||||
* @author Michael Gallego <mic.gallego@gmail.com>
|
||||
*/
|
||||
class DisallowRemoveByReference extends AbstractCollectionStrategy
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hydrate($value)
|
||||
{
|
||||
$collection = $this->getCollectionFromObjectByReference();
|
||||
$collectionArray = $collection->toArray();
|
||||
|
||||
$toAdd = array_udiff($value, $collectionArray, array($this, 'compareObjects'));
|
||||
|
||||
foreach ($toAdd as $element) {
|
||||
$collection->add($element);
|
||||
}
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
72
src/App/Hydrator/Strategy/DisallowRemoveByValue.php
Normal file
72
src/App/Hydrator/Strategy/DisallowRemoveByValue.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* This software consists of voluntary contributions made by many individuals
|
||||
* and is licensed under the MIT license. For more information, see
|
||||
* <http://www.doctrine-project.org>.
|
||||
*/
|
||||
|
||||
namespace App\Hydrator\Strategy;
|
||||
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use LogicException;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
|
||||
/**
|
||||
* When this strategy is used for Collections, if the new collection does not contain elements that are present in
|
||||
* the original collection, then this strategy will not remove those elements. At most, it will add new elements. For
|
||||
* instance, if the collection initially contains elements A and B, and that the new collection contains elements B
|
||||
* and C, then the final collection will contain elements A, B and C.
|
||||
*
|
||||
* This strategy is by value, this means it will use the public API (in this case, remover)
|
||||
*
|
||||
* @license MIT
|
||||
* @link http://www.doctrine-project.org/
|
||||
* @since 0.7.0
|
||||
* @author Michael Gallego <mic.gallego@gmail.com>
|
||||
*/
|
||||
class DisallowRemoveByValue extends AbstractCollectionStrategy
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function hydrate($value)
|
||||
{
|
||||
// AllowRemove strategy need "adder"
|
||||
$adder = 'add' . ucfirst($this->collectionName);
|
||||
|
||||
if (!method_exists($this->object, $adder)) {
|
||||
throw new LogicException(
|
||||
sprintf(
|
||||
'DisallowRemove strategy for DoctrineModule hydrator requires %s to
|
||||
be defined in %s entity domain code, but it seems to be missing',
|
||||
$adder,
|
||||
get_class($this->object)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$collection = $this->getCollectionFromObjectByValue();
|
||||
|
||||
if ($collection instanceof Collection) {
|
||||
$collection = $collection->toArray();
|
||||
}
|
||||
|
||||
$toAdd = new ArrayCollection(array_udiff($value, $collection, array($this, 'compareObjects')));
|
||||
|
||||
$this->object->$adder($toAdd);
|
||||
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
0
templates/.gitkeep
Normal file
0
templates/.gitkeep
Normal file
28
test/AppTest/Action/HomePageActionTest.php
Normal file
28
test/AppTest/Action/HomePageActionTest.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace AppTest\Action;
|
||||
|
||||
use App\Action\HomePageAction;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
use Zend\Expressive\Router\RouterInterface;
|
||||
|
||||
class HomePageActionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/** @var RouterInterface */
|
||||
protected $router;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->router = $this->prophesize(RouterInterface::class);
|
||||
}
|
||||
|
||||
public function testResponse()
|
||||
{
|
||||
$homePage = new HomePageAction($this->router->reveal(), null);
|
||||
$response = $homePage(new ServerRequest(['/']), new Response(), function () {
|
||||
});
|
||||
|
||||
$this->assertTrue($response instanceof Response);
|
||||
}
|
||||
}
|
||||
50
test/AppTest/Action/HomePageFactoryTest.php
Normal file
50
test/AppTest/Action/HomePageFactoryTest.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace AppTest\Action;
|
||||
|
||||
use App\Action\HomePageAction;
|
||||
use App\Action\HomePageFactory;
|
||||
use Interop\Container\ContainerInterface;
|
||||
use Zend\Expressive\Router\RouterInterface;
|
||||
use Zend\Expressive\Template\TemplateRendererInterface;
|
||||
|
||||
class HomePageFactoryTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/** @var ContainerInterface */
|
||||
protected $container;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->container = $this->prophesize(ContainerInterface::class);
|
||||
$router = $this->prophesize(RouterInterface::class);
|
||||
|
||||
$this->container->get(RouterInterface::class)->willReturn($router);
|
||||
}
|
||||
|
||||
public function testFactoryWithoutTemplate()
|
||||
{
|
||||
$factory = new HomePageFactory();
|
||||
$this->container->has(TemplateRendererInterface::class)->willReturn(false);
|
||||
|
||||
$this->assertTrue($factory instanceof HomePageFactory);
|
||||
|
||||
$homePage = $factory($this->container->reveal());
|
||||
|
||||
$this->assertTrue($homePage instanceof HomePageAction);
|
||||
}
|
||||
|
||||
public function testFactoryWithTemplate()
|
||||
{
|
||||
$factory = new HomePageFactory();
|
||||
$this->container->has(TemplateRendererInterface::class)->willReturn(true);
|
||||
$this->container
|
||||
->get(TemplateRendererInterface::class)
|
||||
->willReturn($this->prophesize(TemplateRendererInterface::class));
|
||||
|
||||
$this->assertTrue($factory instanceof HomePageFactory);
|
||||
|
||||
$homePage = $factory($this->container->reveal());
|
||||
|
||||
$this->assertTrue($homePage instanceof HomePageAction);
|
||||
}
|
||||
}
|
||||
22
test/AppTest/Action/PingActionTest.php
Normal file
22
test/AppTest/Action/PingActionTest.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace AppTest\Action;
|
||||
|
||||
use App\Action\PingAction;
|
||||
use Zend\Diactoros\Response;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
|
||||
class PingActionTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testResponse()
|
||||
{
|
||||
$pingAction = new PingAction();
|
||||
$response = $pingAction(new ServerRequest(['/']), new Response(), function () {
|
||||
});
|
||||
$json = json_decode((string) $response->getBody());
|
||||
|
||||
$this->assertTrue($response instanceof Response);
|
||||
$this->assertTrue($response instanceof Response\JsonResponse);
|
||||
$this->assertTrue(isset($json->ack));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user