├── .gitignore ├── Dockerfile ├── README.md ├── composer.json ├── composer.lock ├── docker-compose.yml.dist ├── phpunit.xml └── src ├── Behavioral ├── ChainOfResponsibility │ ├── AbstractValidator.php │ ├── IsGreaterThan.php │ ├── IsLessThan.php │ ├── IsNotNull.php │ ├── IsString.php │ ├── Test │ │ └── ChainTest.php │ └── ValidatorChain.php ├── Command │ ├── Author.php │ ├── CommandInterface.php │ ├── CommandInvoker.php │ ├── InteractiveInterface.php │ ├── Like.php │ ├── Post.php │ ├── Test │ │ ├── CommandInvokerTest.php │ │ ├── LikeTest.php │ │ └── WowTest.php │ └── Wow.php ├── Memento │ ├── Package.php │ ├── PackageSnapshot.php │ └── Test │ │ └── PackageTest.php ├── NullObject │ ├── Client.php │ ├── ClientType.php │ ├── ClientTypeInterface.php │ ├── NullClientType.php │ └── Test │ │ └── ClientTest.php ├── Observer │ ├── Conference.php │ ├── ConferenceStatistic.php │ ├── InvalidConferenceTypeException.php │ ├── Participant.php │ ├── Sponsor.php │ ├── Test │ │ ├── ConferenceStatisticTest.php │ │ ├── ConferenceTest.php │ │ ├── ParticipantTest.php │ │ └── SponsorTest.php │ └── Type.php ├── State │ ├── Closed.php │ ├── InProgress.php │ ├── InvalidStateException.php │ ├── Open.php │ ├── Reopened.php │ ├── Resolved.php │ ├── State.php │ ├── StateInterface.php │ ├── Task.php │ └── Test │ │ └── TaskTest.php ├── Strategy │ ├── EmailNotifier.php │ ├── NotificationPreference.php │ ├── NotifyInterface.php │ ├── SystemNotifier.php │ ├── Test │ │ └── UserNotificationTest.php │ └── UserNotification.php ├── TemplateMethod │ ├── AbstractFileLogSynchronizer.php │ ├── CsvFileLogSynchronizer.php │ ├── Log.php │ ├── LogRepositoryInterface.php │ ├── Test │ │ ├── CsvFileLogSynchronizerTest.php │ │ ├── FakeLogRepository.php │ │ └── TxtFileLogSynchronizerTest.php │ ├── TxtFileLogSynchronizer.php │ └── synchronization │ │ └── files │ │ ├── test_logs.csv │ │ └── test_logs.txt └── Visitor │ ├── LastMinuteCalculator.php │ ├── Participant.php │ ├── PriceCalculatorInterface.php │ ├── Speaker.php │ ├── Sponsor.php │ ├── Status.php │ ├── Test │ ├── ParticipantTest.php │ ├── SpeakerTest.php │ └── SponsorTest.php │ └── VisitableByCalculatorInterface.php ├── Creational ├── AbstractFactory │ ├── AbstractMealFactory.php │ ├── BreakfastInterface.php │ ├── DinnerInterface.php │ ├── Test │ │ ├── VeganMealFactoryTest.php │ │ └── VegetarianMealFactoryTest.php │ ├── VeganBreakfast.php │ ├── VeganDinner.php │ ├── VeganMealFactory.php │ ├── VegetarianBreakfast.php │ ├── VegetarianDinner.php │ └── VegetarianMealFactory.php ├── Builder │ ├── AbstractAgreement.php │ ├── AgreementBuilderInterface.php │ ├── AgreementDirector.php │ ├── B2BContract.php │ ├── B2BContractBuilder.php │ ├── EmploymentContract.php │ ├── EmploymentContractBuilder.php │ └── Test │ │ └── AgreementDirectorTest.php ├── FactoryMethod │ ├── MealFactoryInterface.php │ ├── MealInterface.php │ ├── Test │ │ ├── VeganMealFactoryTest.php │ │ └── VegetarianMealFactoryTest.php │ ├── VeganMeal.php │ ├── VeganMealFactory.php │ ├── VegetarianMeal.php │ └── VegetarianMealFactory.php ├── Prototype │ ├── City.php │ ├── Event.php │ ├── EventPrototypeInterface.php │ ├── Invitation.php │ ├── Place.php │ └── Test │ │ └── EventTest.php ├── SimpleFactory │ ├── MealFactory.php │ ├── MealInterface.php │ ├── MealType.php │ ├── Test │ │ └── MealFactoryTest.php │ ├── VeganMeal.php │ └── VegetarianMeal.php └── Singleton │ ├── Config.php │ └── Test │ └── ConfigTest.php └── Structural ├── Adapter ├── ExternalLogger.php ├── LogAdapter.php ├── LoggerInterface.php └── Test │ └── LogAdapterTest.php ├── Bridge ├── AbstractBenefit.php ├── HealthCare.php ├── JobLevelInterface.php ├── Junior.php ├── Senior.php ├── Test │ ├── HealthCareTest.php │ └── TrainingBudgetTest.php └── TrainingBudget.php ├── Composite ├── Directory.php ├── File.php ├── FileSystemElementInterface.php └── Test │ └── DirectoryTest.php ├── Decorator ├── AbstractOptionDecorator.php ├── Accommodation.php ├── Catering.php ├── Mascot.php ├── OptionInterface.php ├── Sticker.php ├── Test │ └── OptionDecoratorTest.php └── Ticket.php ├── Facade ├── EmailModule │ ├── CloudApiSender.php │ ├── CloudClientInterface.php │ ├── SmtpSender.php │ └── UserNotFoundException.php ├── NotificationFacade.php ├── SmsModule │ └── SmsNotifier.php ├── Test │ └── NotificationFacadeTest.php └── User.php ├── Iterator ├── Directory.php ├── File.php ├── FileSystemElementCollection.php ├── FileSystemElementInterface.php └── Test │ ├── DirectoryTest.php │ └── FileSystemElementCollectionTest.php └── Proxy ├── Base64EncodingInterface.php ├── FileEncoder.php ├── FileEncoderProxy.php ├── FileNotFoundException.php └── Test └── FileEncoderProxyTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .phpunit.result.cache 2 | docker-compose.yml 3 | /vendor -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.4-cli-alpine 2 | 3 | RUN apk add --no-cache libzip-dev zip \ 4 | && docker-php-ext-install zip \ 5 | && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 6 | 7 | WORKDIR /var/www/html 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Design Patterns 2 | **Learn the OOP mechanisms with simple examples...** 3 | 4 | - - - 5 | 6 | **For polish community I also wrote more about patterns on my blog:** 7 | 8 | [Introduction](https://koddlo.pl/wzorce-projektowe-w-php/) 9 | 10 | Creational: 11 | - [Abstract Factory](https://koddlo.pl/abstract-factory-fabryka-abstrakcyjna/) 12 | - [Builder](https://koddlo.pl/builder-budowniczy/) 13 | - [Factory Method](https://koddlo.pl/factory-method-metoda-wytworcza/) 14 | - [Prototype](https://koddlo.pl/prototype-prototyp/) 15 | - [Simple Factory](https://koddlo.pl/simple-factory-prosta-fabryka/) 16 | - [Singleton](https://koddlo.pl/singleton/) 17 | 18 | Structural: 19 | - [Adapter](https://koddlo.pl/adapter-adapter/) 20 | - [Bridge](https://koddlo.pl/bridge-most/) 21 | - [Composite](https://koddlo.pl/composite-kompozyt/) 22 | - [Decorator](https://koddlo.pl/decorator-dekorator/) 23 | - [Facade](https://koddlo.pl/facade-fasada/) 24 | - [Proxy](https://koddlo.pl/proxy-pelnomocnik/) 25 | 26 | Behavioral: 27 | - [Chain Of Responsibility](https://koddlo.pl/chain-of-responsibility-lancuch-zobowiazan/) 28 | - [Command](https://koddlo.pl/command-polecenie/) 29 | - [Iterator](https://koddlo.pl/iterator/) 30 | - [Memento](https://koddlo.pl/memento-pamiatka/) 31 | - [Null Object](https://koddlo.pl/null-object-pusty-obiekt/) 32 | - [Observer](https://koddlo.pl/observer-obserwator/) 33 | - [State](https://koddlo.pl/state-stan/) 34 | - [Strategy](https://koddlo.pl/strategy-strategia/) 35 | - [Template Method](https://koddlo.pl/template-method-metoda-szablonowa/) 36 | - [Visitor](https://koddlo.pl/visitor-odwiedzajacy/) 37 | 38 | I also recommend you two great websites with design patterns: 39 | 40 | - The best materials on this topic that I saw ever (must see): https://refactoring.guru/design-patterns 41 | - Great examples of code (always updated to the newest version of PHP): https://designpatternsphp.readthedocs.io 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koddlo/design-patterns", 3 | "description": "The Design Patterns in PHP", 4 | "keywords": ["design patterns", "php", "koddlo"], 5 | "license": "MIT", 6 | "type": "project", 7 | "autoload": { 8 | "psr-4": { 9 | "DesignPatterns\\Creational\\": "src/Creational", 10 | "DesignPatterns\\Behavioral\\": "src/Behavioral", 11 | "DesignPatterns\\Structural\\": "src/Structural" 12 | } 13 | }, 14 | "require": { 15 | "php": "8.4.*" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "^11" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "2f3ca507203b6e0afc1533c1169004a4", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "myclabs/deep-copy", 12 | "version": "1.12.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/myclabs/DeepCopy.git", 16 | "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", 21 | "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1 || ^8.0" 26 | }, 27 | "conflict": { 28 | "doctrine/collections": "<1.6.8", 29 | "doctrine/common": "<2.13.3 || >=3 <3.2.2" 30 | }, 31 | "require-dev": { 32 | "doctrine/collections": "^1.6.8", 33 | "doctrine/common": "^2.13.3 || ^3.2.2", 34 | "phpspec/prophecy": "^1.10", 35 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 36 | }, 37 | "type": "library", 38 | "autoload": { 39 | "files": [ 40 | "src/DeepCopy/deep_copy.php" 41 | ], 42 | "psr-4": { 43 | "DeepCopy\\": "src/DeepCopy/" 44 | } 45 | }, 46 | "notification-url": "https://packagist.org/downloads/", 47 | "license": [ 48 | "MIT" 49 | ], 50 | "description": "Create deep copies (clones) of your objects", 51 | "keywords": [ 52 | "clone", 53 | "copy", 54 | "duplicate", 55 | "object", 56 | "object graph" 57 | ], 58 | "support": { 59 | "issues": "https://github.com/myclabs/DeepCopy/issues", 60 | "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" 61 | }, 62 | "funding": [ 63 | { 64 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 65 | "type": "tidelift" 66 | } 67 | ], 68 | "time": "2024-11-08T17:47:46+00:00" 69 | }, 70 | { 71 | "name": "nikic/php-parser", 72 | "version": "v5.3.1", 73 | "source": { 74 | "type": "git", 75 | "url": "https://github.com/nikic/PHP-Parser.git", 76 | "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" 77 | }, 78 | "dist": { 79 | "type": "zip", 80 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", 81 | "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", 82 | "shasum": "" 83 | }, 84 | "require": { 85 | "ext-ctype": "*", 86 | "ext-json": "*", 87 | "ext-tokenizer": "*", 88 | "php": ">=7.4" 89 | }, 90 | "require-dev": { 91 | "ircmaxell/php-yacc": "^0.0.7", 92 | "phpunit/phpunit": "^9.0" 93 | }, 94 | "bin": [ 95 | "bin/php-parse" 96 | ], 97 | "type": "library", 98 | "extra": { 99 | "branch-alias": { 100 | "dev-master": "5.0-dev" 101 | } 102 | }, 103 | "autoload": { 104 | "psr-4": { 105 | "PhpParser\\": "lib/PhpParser" 106 | } 107 | }, 108 | "notification-url": "https://packagist.org/downloads/", 109 | "license": [ 110 | "BSD-3-Clause" 111 | ], 112 | "authors": [ 113 | { 114 | "name": "Nikita Popov" 115 | } 116 | ], 117 | "description": "A PHP parser written in PHP", 118 | "keywords": [ 119 | "parser", 120 | "php" 121 | ], 122 | "support": { 123 | "issues": "https://github.com/nikic/PHP-Parser/issues", 124 | "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" 125 | }, 126 | "time": "2024-10-08T18:51:32+00:00" 127 | }, 128 | { 129 | "name": "phar-io/manifest", 130 | "version": "2.0.4", 131 | "source": { 132 | "type": "git", 133 | "url": "https://github.com/phar-io/manifest.git", 134 | "reference": "54750ef60c58e43759730615a392c31c80e23176" 135 | }, 136 | "dist": { 137 | "type": "zip", 138 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", 139 | "reference": "54750ef60c58e43759730615a392c31c80e23176", 140 | "shasum": "" 141 | }, 142 | "require": { 143 | "ext-dom": "*", 144 | "ext-libxml": "*", 145 | "ext-phar": "*", 146 | "ext-xmlwriter": "*", 147 | "phar-io/version": "^3.0.1", 148 | "php": "^7.2 || ^8.0" 149 | }, 150 | "type": "library", 151 | "extra": { 152 | "branch-alias": { 153 | "dev-master": "2.0.x-dev" 154 | } 155 | }, 156 | "autoload": { 157 | "classmap": [ 158 | "src/" 159 | ] 160 | }, 161 | "notification-url": "https://packagist.org/downloads/", 162 | "license": [ 163 | "BSD-3-Clause" 164 | ], 165 | "authors": [ 166 | { 167 | "name": "Arne Blankerts", 168 | "email": "arne@blankerts.de", 169 | "role": "Developer" 170 | }, 171 | { 172 | "name": "Sebastian Heuer", 173 | "email": "sebastian@phpeople.de", 174 | "role": "Developer" 175 | }, 176 | { 177 | "name": "Sebastian Bergmann", 178 | "email": "sebastian@phpunit.de", 179 | "role": "Developer" 180 | } 181 | ], 182 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 183 | "support": { 184 | "issues": "https://github.com/phar-io/manifest/issues", 185 | "source": "https://github.com/phar-io/manifest/tree/2.0.4" 186 | }, 187 | "funding": [ 188 | { 189 | "url": "https://github.com/theseer", 190 | "type": "github" 191 | } 192 | ], 193 | "time": "2024-03-03T12:33:53+00:00" 194 | }, 195 | { 196 | "name": "phar-io/version", 197 | "version": "3.2.1", 198 | "source": { 199 | "type": "git", 200 | "url": "https://github.com/phar-io/version.git", 201 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 202 | }, 203 | "dist": { 204 | "type": "zip", 205 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 206 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 207 | "shasum": "" 208 | }, 209 | "require": { 210 | "php": "^7.2 || ^8.0" 211 | }, 212 | "type": "library", 213 | "autoload": { 214 | "classmap": [ 215 | "src/" 216 | ] 217 | }, 218 | "notification-url": "https://packagist.org/downloads/", 219 | "license": [ 220 | "BSD-3-Clause" 221 | ], 222 | "authors": [ 223 | { 224 | "name": "Arne Blankerts", 225 | "email": "arne@blankerts.de", 226 | "role": "Developer" 227 | }, 228 | { 229 | "name": "Sebastian Heuer", 230 | "email": "sebastian@phpeople.de", 231 | "role": "Developer" 232 | }, 233 | { 234 | "name": "Sebastian Bergmann", 235 | "email": "sebastian@phpunit.de", 236 | "role": "Developer" 237 | } 238 | ], 239 | "description": "Library for handling version information and constraints", 240 | "support": { 241 | "issues": "https://github.com/phar-io/version/issues", 242 | "source": "https://github.com/phar-io/version/tree/3.2.1" 243 | }, 244 | "time": "2022-02-21T01:04:05+00:00" 245 | }, 246 | { 247 | "name": "phpunit/php-code-coverage", 248 | "version": "11.0.7", 249 | "source": { 250 | "type": "git", 251 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 252 | "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" 253 | }, 254 | "dist": { 255 | "type": "zip", 256 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", 257 | "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", 258 | "shasum": "" 259 | }, 260 | "require": { 261 | "ext-dom": "*", 262 | "ext-libxml": "*", 263 | "ext-xmlwriter": "*", 264 | "nikic/php-parser": "^5.3.1", 265 | "php": ">=8.2", 266 | "phpunit/php-file-iterator": "^5.1.0", 267 | "phpunit/php-text-template": "^4.0.1", 268 | "sebastian/code-unit-reverse-lookup": "^4.0.1", 269 | "sebastian/complexity": "^4.0.1", 270 | "sebastian/environment": "^7.2.0", 271 | "sebastian/lines-of-code": "^3.0.1", 272 | "sebastian/version": "^5.0.2", 273 | "theseer/tokenizer": "^1.2.3" 274 | }, 275 | "require-dev": { 276 | "phpunit/phpunit": "^11.4.1" 277 | }, 278 | "suggest": { 279 | "ext-pcov": "PHP extension that provides line coverage", 280 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 281 | }, 282 | "type": "library", 283 | "extra": { 284 | "branch-alias": { 285 | "dev-main": "11.0.x-dev" 286 | } 287 | }, 288 | "autoload": { 289 | "classmap": [ 290 | "src/" 291 | ] 292 | }, 293 | "notification-url": "https://packagist.org/downloads/", 294 | "license": [ 295 | "BSD-3-Clause" 296 | ], 297 | "authors": [ 298 | { 299 | "name": "Sebastian Bergmann", 300 | "email": "sebastian@phpunit.de", 301 | "role": "lead" 302 | } 303 | ], 304 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 305 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 306 | "keywords": [ 307 | "coverage", 308 | "testing", 309 | "xunit" 310 | ], 311 | "support": { 312 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 313 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", 314 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" 315 | }, 316 | "funding": [ 317 | { 318 | "url": "https://github.com/sebastianbergmann", 319 | "type": "github" 320 | } 321 | ], 322 | "time": "2024-10-09T06:21:38+00:00" 323 | }, 324 | { 325 | "name": "phpunit/php-file-iterator", 326 | "version": "5.1.0", 327 | "source": { 328 | "type": "git", 329 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 330 | "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" 331 | }, 332 | "dist": { 333 | "type": "zip", 334 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", 335 | "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", 336 | "shasum": "" 337 | }, 338 | "require": { 339 | "php": ">=8.2" 340 | }, 341 | "require-dev": { 342 | "phpunit/phpunit": "^11.0" 343 | }, 344 | "type": "library", 345 | "extra": { 346 | "branch-alias": { 347 | "dev-main": "5.0-dev" 348 | } 349 | }, 350 | "autoload": { 351 | "classmap": [ 352 | "src/" 353 | ] 354 | }, 355 | "notification-url": "https://packagist.org/downloads/", 356 | "license": [ 357 | "BSD-3-Clause" 358 | ], 359 | "authors": [ 360 | { 361 | "name": "Sebastian Bergmann", 362 | "email": "sebastian@phpunit.de", 363 | "role": "lead" 364 | } 365 | ], 366 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 367 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 368 | "keywords": [ 369 | "filesystem", 370 | "iterator" 371 | ], 372 | "support": { 373 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 374 | "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", 375 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" 376 | }, 377 | "funding": [ 378 | { 379 | "url": "https://github.com/sebastianbergmann", 380 | "type": "github" 381 | } 382 | ], 383 | "time": "2024-08-27T05:02:59+00:00" 384 | }, 385 | { 386 | "name": "phpunit/php-invoker", 387 | "version": "5.0.1", 388 | "source": { 389 | "type": "git", 390 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 391 | "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" 392 | }, 393 | "dist": { 394 | "type": "zip", 395 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", 396 | "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", 397 | "shasum": "" 398 | }, 399 | "require": { 400 | "php": ">=8.2" 401 | }, 402 | "require-dev": { 403 | "ext-pcntl": "*", 404 | "phpunit/phpunit": "^11.0" 405 | }, 406 | "suggest": { 407 | "ext-pcntl": "*" 408 | }, 409 | "type": "library", 410 | "extra": { 411 | "branch-alias": { 412 | "dev-main": "5.0-dev" 413 | } 414 | }, 415 | "autoload": { 416 | "classmap": [ 417 | "src/" 418 | ] 419 | }, 420 | "notification-url": "https://packagist.org/downloads/", 421 | "license": [ 422 | "BSD-3-Clause" 423 | ], 424 | "authors": [ 425 | { 426 | "name": "Sebastian Bergmann", 427 | "email": "sebastian@phpunit.de", 428 | "role": "lead" 429 | } 430 | ], 431 | "description": "Invoke callables with a timeout", 432 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 433 | "keywords": [ 434 | "process" 435 | ], 436 | "support": { 437 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 438 | "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", 439 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" 440 | }, 441 | "funding": [ 442 | { 443 | "url": "https://github.com/sebastianbergmann", 444 | "type": "github" 445 | } 446 | ], 447 | "time": "2024-07-03T05:07:44+00:00" 448 | }, 449 | { 450 | "name": "phpunit/php-text-template", 451 | "version": "4.0.1", 452 | "source": { 453 | "type": "git", 454 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 455 | "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" 456 | }, 457 | "dist": { 458 | "type": "zip", 459 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", 460 | "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", 461 | "shasum": "" 462 | }, 463 | "require": { 464 | "php": ">=8.2" 465 | }, 466 | "require-dev": { 467 | "phpunit/phpunit": "^11.0" 468 | }, 469 | "type": "library", 470 | "extra": { 471 | "branch-alias": { 472 | "dev-main": "4.0-dev" 473 | } 474 | }, 475 | "autoload": { 476 | "classmap": [ 477 | "src/" 478 | ] 479 | }, 480 | "notification-url": "https://packagist.org/downloads/", 481 | "license": [ 482 | "BSD-3-Clause" 483 | ], 484 | "authors": [ 485 | { 486 | "name": "Sebastian Bergmann", 487 | "email": "sebastian@phpunit.de", 488 | "role": "lead" 489 | } 490 | ], 491 | "description": "Simple template engine.", 492 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 493 | "keywords": [ 494 | "template" 495 | ], 496 | "support": { 497 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 498 | "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", 499 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" 500 | }, 501 | "funding": [ 502 | { 503 | "url": "https://github.com/sebastianbergmann", 504 | "type": "github" 505 | } 506 | ], 507 | "time": "2024-07-03T05:08:43+00:00" 508 | }, 509 | { 510 | "name": "phpunit/php-timer", 511 | "version": "7.0.1", 512 | "source": { 513 | "type": "git", 514 | "url": "https://github.com/sebastianbergmann/php-timer.git", 515 | "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" 516 | }, 517 | "dist": { 518 | "type": "zip", 519 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", 520 | "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", 521 | "shasum": "" 522 | }, 523 | "require": { 524 | "php": ">=8.2" 525 | }, 526 | "require-dev": { 527 | "phpunit/phpunit": "^11.0" 528 | }, 529 | "type": "library", 530 | "extra": { 531 | "branch-alias": { 532 | "dev-main": "7.0-dev" 533 | } 534 | }, 535 | "autoload": { 536 | "classmap": [ 537 | "src/" 538 | ] 539 | }, 540 | "notification-url": "https://packagist.org/downloads/", 541 | "license": [ 542 | "BSD-3-Clause" 543 | ], 544 | "authors": [ 545 | { 546 | "name": "Sebastian Bergmann", 547 | "email": "sebastian@phpunit.de", 548 | "role": "lead" 549 | } 550 | ], 551 | "description": "Utility class for timing", 552 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 553 | "keywords": [ 554 | "timer" 555 | ], 556 | "support": { 557 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 558 | "security": "https://github.com/sebastianbergmann/php-timer/security/policy", 559 | "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" 560 | }, 561 | "funding": [ 562 | { 563 | "url": "https://github.com/sebastianbergmann", 564 | "type": "github" 565 | } 566 | ], 567 | "time": "2024-07-03T05:09:35+00:00" 568 | }, 569 | { 570 | "name": "phpunit/phpunit", 571 | "version": "11.4.3", 572 | "source": { 573 | "type": "git", 574 | "url": "https://github.com/sebastianbergmann/phpunit.git", 575 | "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76" 576 | }, 577 | "dist": { 578 | "type": "zip", 579 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e8e8ed1854de5d36c088ec1833beae40d2dedd76", 580 | "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76", 581 | "shasum": "" 582 | }, 583 | "require": { 584 | "ext-dom": "*", 585 | "ext-json": "*", 586 | "ext-libxml": "*", 587 | "ext-mbstring": "*", 588 | "ext-xml": "*", 589 | "ext-xmlwriter": "*", 590 | "myclabs/deep-copy": "^1.12.0", 591 | "phar-io/manifest": "^2.0.4", 592 | "phar-io/version": "^3.2.1", 593 | "php": ">=8.2", 594 | "phpunit/php-code-coverage": "^11.0.7", 595 | "phpunit/php-file-iterator": "^5.1.0", 596 | "phpunit/php-invoker": "^5.0.1", 597 | "phpunit/php-text-template": "^4.0.1", 598 | "phpunit/php-timer": "^7.0.1", 599 | "sebastian/cli-parser": "^3.0.2", 600 | "sebastian/code-unit": "^3.0.1", 601 | "sebastian/comparator": "^6.1.1", 602 | "sebastian/diff": "^6.0.2", 603 | "sebastian/environment": "^7.2.0", 604 | "sebastian/exporter": "^6.1.3", 605 | "sebastian/global-state": "^7.0.2", 606 | "sebastian/object-enumerator": "^6.0.1", 607 | "sebastian/type": "^5.1.0", 608 | "sebastian/version": "^5.0.2" 609 | }, 610 | "suggest": { 611 | "ext-soap": "To be able to generate mocks based on WSDL files" 612 | }, 613 | "bin": [ 614 | "phpunit" 615 | ], 616 | "type": "library", 617 | "extra": { 618 | "branch-alias": { 619 | "dev-main": "11.4-dev" 620 | } 621 | }, 622 | "autoload": { 623 | "files": [ 624 | "src/Framework/Assert/Functions.php" 625 | ], 626 | "classmap": [ 627 | "src/" 628 | ] 629 | }, 630 | "notification-url": "https://packagist.org/downloads/", 631 | "license": [ 632 | "BSD-3-Clause" 633 | ], 634 | "authors": [ 635 | { 636 | "name": "Sebastian Bergmann", 637 | "email": "sebastian@phpunit.de", 638 | "role": "lead" 639 | } 640 | ], 641 | "description": "The PHP Unit Testing framework.", 642 | "homepage": "https://phpunit.de/", 643 | "keywords": [ 644 | "phpunit", 645 | "testing", 646 | "xunit" 647 | ], 648 | "support": { 649 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 650 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy", 651 | "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.3" 652 | }, 653 | "funding": [ 654 | { 655 | "url": "https://phpunit.de/sponsors.html", 656 | "type": "custom" 657 | }, 658 | { 659 | "url": "https://github.com/sebastianbergmann", 660 | "type": "github" 661 | }, 662 | { 663 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 664 | "type": "tidelift" 665 | } 666 | ], 667 | "time": "2024-10-28T13:07:50+00:00" 668 | }, 669 | { 670 | "name": "sebastian/cli-parser", 671 | "version": "3.0.2", 672 | "source": { 673 | "type": "git", 674 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 675 | "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" 676 | }, 677 | "dist": { 678 | "type": "zip", 679 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", 680 | "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", 681 | "shasum": "" 682 | }, 683 | "require": { 684 | "php": ">=8.2" 685 | }, 686 | "require-dev": { 687 | "phpunit/phpunit": "^11.0" 688 | }, 689 | "type": "library", 690 | "extra": { 691 | "branch-alias": { 692 | "dev-main": "3.0-dev" 693 | } 694 | }, 695 | "autoload": { 696 | "classmap": [ 697 | "src/" 698 | ] 699 | }, 700 | "notification-url": "https://packagist.org/downloads/", 701 | "license": [ 702 | "BSD-3-Clause" 703 | ], 704 | "authors": [ 705 | { 706 | "name": "Sebastian Bergmann", 707 | "email": "sebastian@phpunit.de", 708 | "role": "lead" 709 | } 710 | ], 711 | "description": "Library for parsing CLI options", 712 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 713 | "support": { 714 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 715 | "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", 716 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" 717 | }, 718 | "funding": [ 719 | { 720 | "url": "https://github.com/sebastianbergmann", 721 | "type": "github" 722 | } 723 | ], 724 | "time": "2024-07-03T04:41:36+00:00" 725 | }, 726 | { 727 | "name": "sebastian/code-unit", 728 | "version": "3.0.1", 729 | "source": { 730 | "type": "git", 731 | "url": "https://github.com/sebastianbergmann/code-unit.git", 732 | "reference": "6bb7d09d6623567178cf54126afa9c2310114268" 733 | }, 734 | "dist": { 735 | "type": "zip", 736 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", 737 | "reference": "6bb7d09d6623567178cf54126afa9c2310114268", 738 | "shasum": "" 739 | }, 740 | "require": { 741 | "php": ">=8.2" 742 | }, 743 | "require-dev": { 744 | "phpunit/phpunit": "^11.0" 745 | }, 746 | "type": "library", 747 | "extra": { 748 | "branch-alias": { 749 | "dev-main": "3.0-dev" 750 | } 751 | }, 752 | "autoload": { 753 | "classmap": [ 754 | "src/" 755 | ] 756 | }, 757 | "notification-url": "https://packagist.org/downloads/", 758 | "license": [ 759 | "BSD-3-Clause" 760 | ], 761 | "authors": [ 762 | { 763 | "name": "Sebastian Bergmann", 764 | "email": "sebastian@phpunit.de", 765 | "role": "lead" 766 | } 767 | ], 768 | "description": "Collection of value objects that represent the PHP code units", 769 | "homepage": "https://github.com/sebastianbergmann/code-unit", 770 | "support": { 771 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 772 | "security": "https://github.com/sebastianbergmann/code-unit/security/policy", 773 | "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" 774 | }, 775 | "funding": [ 776 | { 777 | "url": "https://github.com/sebastianbergmann", 778 | "type": "github" 779 | } 780 | ], 781 | "time": "2024-07-03T04:44:28+00:00" 782 | }, 783 | { 784 | "name": "sebastian/code-unit-reverse-lookup", 785 | "version": "4.0.1", 786 | "source": { 787 | "type": "git", 788 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 789 | "reference": "183a9b2632194febd219bb9246eee421dad8d45e" 790 | }, 791 | "dist": { 792 | "type": "zip", 793 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", 794 | "reference": "183a9b2632194febd219bb9246eee421dad8d45e", 795 | "shasum": "" 796 | }, 797 | "require": { 798 | "php": ">=8.2" 799 | }, 800 | "require-dev": { 801 | "phpunit/phpunit": "^11.0" 802 | }, 803 | "type": "library", 804 | "extra": { 805 | "branch-alias": { 806 | "dev-main": "4.0-dev" 807 | } 808 | }, 809 | "autoload": { 810 | "classmap": [ 811 | "src/" 812 | ] 813 | }, 814 | "notification-url": "https://packagist.org/downloads/", 815 | "license": [ 816 | "BSD-3-Clause" 817 | ], 818 | "authors": [ 819 | { 820 | "name": "Sebastian Bergmann", 821 | "email": "sebastian@phpunit.de" 822 | } 823 | ], 824 | "description": "Looks up which function or method a line of code belongs to", 825 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 826 | "support": { 827 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 828 | "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", 829 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" 830 | }, 831 | "funding": [ 832 | { 833 | "url": "https://github.com/sebastianbergmann", 834 | "type": "github" 835 | } 836 | ], 837 | "time": "2024-07-03T04:45:54+00:00" 838 | }, 839 | { 840 | "name": "sebastian/comparator", 841 | "version": "6.2.1", 842 | "source": { 843 | "type": "git", 844 | "url": "https://github.com/sebastianbergmann/comparator.git", 845 | "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" 846 | }, 847 | "dist": { 848 | "type": "zip", 849 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", 850 | "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", 851 | "shasum": "" 852 | }, 853 | "require": { 854 | "ext-dom": "*", 855 | "ext-mbstring": "*", 856 | "php": ">=8.2", 857 | "sebastian/diff": "^6.0", 858 | "sebastian/exporter": "^6.0" 859 | }, 860 | "require-dev": { 861 | "phpunit/phpunit": "^11.4" 862 | }, 863 | "type": "library", 864 | "extra": { 865 | "branch-alias": { 866 | "dev-main": "6.2-dev" 867 | } 868 | }, 869 | "autoload": { 870 | "classmap": [ 871 | "src/" 872 | ] 873 | }, 874 | "notification-url": "https://packagist.org/downloads/", 875 | "license": [ 876 | "BSD-3-Clause" 877 | ], 878 | "authors": [ 879 | { 880 | "name": "Sebastian Bergmann", 881 | "email": "sebastian@phpunit.de" 882 | }, 883 | { 884 | "name": "Jeff Welch", 885 | "email": "whatthejeff@gmail.com" 886 | }, 887 | { 888 | "name": "Volker Dusch", 889 | "email": "github@wallbash.com" 890 | }, 891 | { 892 | "name": "Bernhard Schussek", 893 | "email": "bschussek@2bepublished.at" 894 | } 895 | ], 896 | "description": "Provides the functionality to compare PHP values for equality", 897 | "homepage": "https://github.com/sebastianbergmann/comparator", 898 | "keywords": [ 899 | "comparator", 900 | "compare", 901 | "equality" 902 | ], 903 | "support": { 904 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 905 | "security": "https://github.com/sebastianbergmann/comparator/security/policy", 906 | "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" 907 | }, 908 | "funding": [ 909 | { 910 | "url": "https://github.com/sebastianbergmann", 911 | "type": "github" 912 | } 913 | ], 914 | "time": "2024-10-31T05:30:08+00:00" 915 | }, 916 | { 917 | "name": "sebastian/complexity", 918 | "version": "4.0.1", 919 | "source": { 920 | "type": "git", 921 | "url": "https://github.com/sebastianbergmann/complexity.git", 922 | "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" 923 | }, 924 | "dist": { 925 | "type": "zip", 926 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", 927 | "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", 928 | "shasum": "" 929 | }, 930 | "require": { 931 | "nikic/php-parser": "^5.0", 932 | "php": ">=8.2" 933 | }, 934 | "require-dev": { 935 | "phpunit/phpunit": "^11.0" 936 | }, 937 | "type": "library", 938 | "extra": { 939 | "branch-alias": { 940 | "dev-main": "4.0-dev" 941 | } 942 | }, 943 | "autoload": { 944 | "classmap": [ 945 | "src/" 946 | ] 947 | }, 948 | "notification-url": "https://packagist.org/downloads/", 949 | "license": [ 950 | "BSD-3-Clause" 951 | ], 952 | "authors": [ 953 | { 954 | "name": "Sebastian Bergmann", 955 | "email": "sebastian@phpunit.de", 956 | "role": "lead" 957 | } 958 | ], 959 | "description": "Library for calculating the complexity of PHP code units", 960 | "homepage": "https://github.com/sebastianbergmann/complexity", 961 | "support": { 962 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 963 | "security": "https://github.com/sebastianbergmann/complexity/security/policy", 964 | "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" 965 | }, 966 | "funding": [ 967 | { 968 | "url": "https://github.com/sebastianbergmann", 969 | "type": "github" 970 | } 971 | ], 972 | "time": "2024-07-03T04:49:50+00:00" 973 | }, 974 | { 975 | "name": "sebastian/diff", 976 | "version": "6.0.2", 977 | "source": { 978 | "type": "git", 979 | "url": "https://github.com/sebastianbergmann/diff.git", 980 | "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" 981 | }, 982 | "dist": { 983 | "type": "zip", 984 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", 985 | "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", 986 | "shasum": "" 987 | }, 988 | "require": { 989 | "php": ">=8.2" 990 | }, 991 | "require-dev": { 992 | "phpunit/phpunit": "^11.0", 993 | "symfony/process": "^4.2 || ^5" 994 | }, 995 | "type": "library", 996 | "extra": { 997 | "branch-alias": { 998 | "dev-main": "6.0-dev" 999 | } 1000 | }, 1001 | "autoload": { 1002 | "classmap": [ 1003 | "src/" 1004 | ] 1005 | }, 1006 | "notification-url": "https://packagist.org/downloads/", 1007 | "license": [ 1008 | "BSD-3-Clause" 1009 | ], 1010 | "authors": [ 1011 | { 1012 | "name": "Sebastian Bergmann", 1013 | "email": "sebastian@phpunit.de" 1014 | }, 1015 | { 1016 | "name": "Kore Nordmann", 1017 | "email": "mail@kore-nordmann.de" 1018 | } 1019 | ], 1020 | "description": "Diff implementation", 1021 | "homepage": "https://github.com/sebastianbergmann/diff", 1022 | "keywords": [ 1023 | "diff", 1024 | "udiff", 1025 | "unidiff", 1026 | "unified diff" 1027 | ], 1028 | "support": { 1029 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1030 | "security": "https://github.com/sebastianbergmann/diff/security/policy", 1031 | "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" 1032 | }, 1033 | "funding": [ 1034 | { 1035 | "url": "https://github.com/sebastianbergmann", 1036 | "type": "github" 1037 | } 1038 | ], 1039 | "time": "2024-07-03T04:53:05+00:00" 1040 | }, 1041 | { 1042 | "name": "sebastian/environment", 1043 | "version": "7.2.0", 1044 | "source": { 1045 | "type": "git", 1046 | "url": "https://github.com/sebastianbergmann/environment.git", 1047 | "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" 1048 | }, 1049 | "dist": { 1050 | "type": "zip", 1051 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", 1052 | "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", 1053 | "shasum": "" 1054 | }, 1055 | "require": { 1056 | "php": ">=8.2" 1057 | }, 1058 | "require-dev": { 1059 | "phpunit/phpunit": "^11.0" 1060 | }, 1061 | "suggest": { 1062 | "ext-posix": "*" 1063 | }, 1064 | "type": "library", 1065 | "extra": { 1066 | "branch-alias": { 1067 | "dev-main": "7.2-dev" 1068 | } 1069 | }, 1070 | "autoload": { 1071 | "classmap": [ 1072 | "src/" 1073 | ] 1074 | }, 1075 | "notification-url": "https://packagist.org/downloads/", 1076 | "license": [ 1077 | "BSD-3-Clause" 1078 | ], 1079 | "authors": [ 1080 | { 1081 | "name": "Sebastian Bergmann", 1082 | "email": "sebastian@phpunit.de" 1083 | } 1084 | ], 1085 | "description": "Provides functionality to handle HHVM/PHP environments", 1086 | "homepage": "https://github.com/sebastianbergmann/environment", 1087 | "keywords": [ 1088 | "Xdebug", 1089 | "environment", 1090 | "hhvm" 1091 | ], 1092 | "support": { 1093 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1094 | "security": "https://github.com/sebastianbergmann/environment/security/policy", 1095 | "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" 1096 | }, 1097 | "funding": [ 1098 | { 1099 | "url": "https://github.com/sebastianbergmann", 1100 | "type": "github" 1101 | } 1102 | ], 1103 | "time": "2024-07-03T04:54:44+00:00" 1104 | }, 1105 | { 1106 | "name": "sebastian/exporter", 1107 | "version": "6.1.3", 1108 | "source": { 1109 | "type": "git", 1110 | "url": "https://github.com/sebastianbergmann/exporter.git", 1111 | "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" 1112 | }, 1113 | "dist": { 1114 | "type": "zip", 1115 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", 1116 | "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", 1117 | "shasum": "" 1118 | }, 1119 | "require": { 1120 | "ext-mbstring": "*", 1121 | "php": ">=8.2", 1122 | "sebastian/recursion-context": "^6.0" 1123 | }, 1124 | "require-dev": { 1125 | "phpunit/phpunit": "^11.2" 1126 | }, 1127 | "type": "library", 1128 | "extra": { 1129 | "branch-alias": { 1130 | "dev-main": "6.1-dev" 1131 | } 1132 | }, 1133 | "autoload": { 1134 | "classmap": [ 1135 | "src/" 1136 | ] 1137 | }, 1138 | "notification-url": "https://packagist.org/downloads/", 1139 | "license": [ 1140 | "BSD-3-Clause" 1141 | ], 1142 | "authors": [ 1143 | { 1144 | "name": "Sebastian Bergmann", 1145 | "email": "sebastian@phpunit.de" 1146 | }, 1147 | { 1148 | "name": "Jeff Welch", 1149 | "email": "whatthejeff@gmail.com" 1150 | }, 1151 | { 1152 | "name": "Volker Dusch", 1153 | "email": "github@wallbash.com" 1154 | }, 1155 | { 1156 | "name": "Adam Harvey", 1157 | "email": "aharvey@php.net" 1158 | }, 1159 | { 1160 | "name": "Bernhard Schussek", 1161 | "email": "bschussek@gmail.com" 1162 | } 1163 | ], 1164 | "description": "Provides the functionality to export PHP variables for visualization", 1165 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1166 | "keywords": [ 1167 | "export", 1168 | "exporter" 1169 | ], 1170 | "support": { 1171 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1172 | "security": "https://github.com/sebastianbergmann/exporter/security/policy", 1173 | "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" 1174 | }, 1175 | "funding": [ 1176 | { 1177 | "url": "https://github.com/sebastianbergmann", 1178 | "type": "github" 1179 | } 1180 | ], 1181 | "time": "2024-07-03T04:56:19+00:00" 1182 | }, 1183 | { 1184 | "name": "sebastian/global-state", 1185 | "version": "7.0.2", 1186 | "source": { 1187 | "type": "git", 1188 | "url": "https://github.com/sebastianbergmann/global-state.git", 1189 | "reference": "3be331570a721f9a4b5917f4209773de17f747d7" 1190 | }, 1191 | "dist": { 1192 | "type": "zip", 1193 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", 1194 | "reference": "3be331570a721f9a4b5917f4209773de17f747d7", 1195 | "shasum": "" 1196 | }, 1197 | "require": { 1198 | "php": ">=8.2", 1199 | "sebastian/object-reflector": "^4.0", 1200 | "sebastian/recursion-context": "^6.0" 1201 | }, 1202 | "require-dev": { 1203 | "ext-dom": "*", 1204 | "phpunit/phpunit": "^11.0" 1205 | }, 1206 | "type": "library", 1207 | "extra": { 1208 | "branch-alias": { 1209 | "dev-main": "7.0-dev" 1210 | } 1211 | }, 1212 | "autoload": { 1213 | "classmap": [ 1214 | "src/" 1215 | ] 1216 | }, 1217 | "notification-url": "https://packagist.org/downloads/", 1218 | "license": [ 1219 | "BSD-3-Clause" 1220 | ], 1221 | "authors": [ 1222 | { 1223 | "name": "Sebastian Bergmann", 1224 | "email": "sebastian@phpunit.de" 1225 | } 1226 | ], 1227 | "description": "Snapshotting of global state", 1228 | "homepage": "https://www.github.com/sebastianbergmann/global-state", 1229 | "keywords": [ 1230 | "global state" 1231 | ], 1232 | "support": { 1233 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1234 | "security": "https://github.com/sebastianbergmann/global-state/security/policy", 1235 | "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" 1236 | }, 1237 | "funding": [ 1238 | { 1239 | "url": "https://github.com/sebastianbergmann", 1240 | "type": "github" 1241 | } 1242 | ], 1243 | "time": "2024-07-03T04:57:36+00:00" 1244 | }, 1245 | { 1246 | "name": "sebastian/lines-of-code", 1247 | "version": "3.0.1", 1248 | "source": { 1249 | "type": "git", 1250 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1251 | "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" 1252 | }, 1253 | "dist": { 1254 | "type": "zip", 1255 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", 1256 | "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", 1257 | "shasum": "" 1258 | }, 1259 | "require": { 1260 | "nikic/php-parser": "^5.0", 1261 | "php": ">=8.2" 1262 | }, 1263 | "require-dev": { 1264 | "phpunit/phpunit": "^11.0" 1265 | }, 1266 | "type": "library", 1267 | "extra": { 1268 | "branch-alias": { 1269 | "dev-main": "3.0-dev" 1270 | } 1271 | }, 1272 | "autoload": { 1273 | "classmap": [ 1274 | "src/" 1275 | ] 1276 | }, 1277 | "notification-url": "https://packagist.org/downloads/", 1278 | "license": [ 1279 | "BSD-3-Clause" 1280 | ], 1281 | "authors": [ 1282 | { 1283 | "name": "Sebastian Bergmann", 1284 | "email": "sebastian@phpunit.de", 1285 | "role": "lead" 1286 | } 1287 | ], 1288 | "description": "Library for counting the lines of code in PHP source code", 1289 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1290 | "support": { 1291 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1292 | "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", 1293 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" 1294 | }, 1295 | "funding": [ 1296 | { 1297 | "url": "https://github.com/sebastianbergmann", 1298 | "type": "github" 1299 | } 1300 | ], 1301 | "time": "2024-07-03T04:58:38+00:00" 1302 | }, 1303 | { 1304 | "name": "sebastian/object-enumerator", 1305 | "version": "6.0.1", 1306 | "source": { 1307 | "type": "git", 1308 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1309 | "reference": "f5b498e631a74204185071eb41f33f38d64608aa" 1310 | }, 1311 | "dist": { 1312 | "type": "zip", 1313 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", 1314 | "reference": "f5b498e631a74204185071eb41f33f38d64608aa", 1315 | "shasum": "" 1316 | }, 1317 | "require": { 1318 | "php": ">=8.2", 1319 | "sebastian/object-reflector": "^4.0", 1320 | "sebastian/recursion-context": "^6.0" 1321 | }, 1322 | "require-dev": { 1323 | "phpunit/phpunit": "^11.0" 1324 | }, 1325 | "type": "library", 1326 | "extra": { 1327 | "branch-alias": { 1328 | "dev-main": "6.0-dev" 1329 | } 1330 | }, 1331 | "autoload": { 1332 | "classmap": [ 1333 | "src/" 1334 | ] 1335 | }, 1336 | "notification-url": "https://packagist.org/downloads/", 1337 | "license": [ 1338 | "BSD-3-Clause" 1339 | ], 1340 | "authors": [ 1341 | { 1342 | "name": "Sebastian Bergmann", 1343 | "email": "sebastian@phpunit.de" 1344 | } 1345 | ], 1346 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1347 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1348 | "support": { 1349 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1350 | "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", 1351 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" 1352 | }, 1353 | "funding": [ 1354 | { 1355 | "url": "https://github.com/sebastianbergmann", 1356 | "type": "github" 1357 | } 1358 | ], 1359 | "time": "2024-07-03T05:00:13+00:00" 1360 | }, 1361 | { 1362 | "name": "sebastian/object-reflector", 1363 | "version": "4.0.1", 1364 | "source": { 1365 | "type": "git", 1366 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1367 | "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" 1368 | }, 1369 | "dist": { 1370 | "type": "zip", 1371 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", 1372 | "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", 1373 | "shasum": "" 1374 | }, 1375 | "require": { 1376 | "php": ">=8.2" 1377 | }, 1378 | "require-dev": { 1379 | "phpunit/phpunit": "^11.0" 1380 | }, 1381 | "type": "library", 1382 | "extra": { 1383 | "branch-alias": { 1384 | "dev-main": "4.0-dev" 1385 | } 1386 | }, 1387 | "autoload": { 1388 | "classmap": [ 1389 | "src/" 1390 | ] 1391 | }, 1392 | "notification-url": "https://packagist.org/downloads/", 1393 | "license": [ 1394 | "BSD-3-Clause" 1395 | ], 1396 | "authors": [ 1397 | { 1398 | "name": "Sebastian Bergmann", 1399 | "email": "sebastian@phpunit.de" 1400 | } 1401 | ], 1402 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1403 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1404 | "support": { 1405 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1406 | "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", 1407 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" 1408 | }, 1409 | "funding": [ 1410 | { 1411 | "url": "https://github.com/sebastianbergmann", 1412 | "type": "github" 1413 | } 1414 | ], 1415 | "time": "2024-07-03T05:01:32+00:00" 1416 | }, 1417 | { 1418 | "name": "sebastian/recursion-context", 1419 | "version": "6.0.2", 1420 | "source": { 1421 | "type": "git", 1422 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1423 | "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" 1424 | }, 1425 | "dist": { 1426 | "type": "zip", 1427 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", 1428 | "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", 1429 | "shasum": "" 1430 | }, 1431 | "require": { 1432 | "php": ">=8.2" 1433 | }, 1434 | "require-dev": { 1435 | "phpunit/phpunit": "^11.0" 1436 | }, 1437 | "type": "library", 1438 | "extra": { 1439 | "branch-alias": { 1440 | "dev-main": "6.0-dev" 1441 | } 1442 | }, 1443 | "autoload": { 1444 | "classmap": [ 1445 | "src/" 1446 | ] 1447 | }, 1448 | "notification-url": "https://packagist.org/downloads/", 1449 | "license": [ 1450 | "BSD-3-Clause" 1451 | ], 1452 | "authors": [ 1453 | { 1454 | "name": "Sebastian Bergmann", 1455 | "email": "sebastian@phpunit.de" 1456 | }, 1457 | { 1458 | "name": "Jeff Welch", 1459 | "email": "whatthejeff@gmail.com" 1460 | }, 1461 | { 1462 | "name": "Adam Harvey", 1463 | "email": "aharvey@php.net" 1464 | } 1465 | ], 1466 | "description": "Provides functionality to recursively process PHP variables", 1467 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 1468 | "support": { 1469 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1470 | "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", 1471 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" 1472 | }, 1473 | "funding": [ 1474 | { 1475 | "url": "https://github.com/sebastianbergmann", 1476 | "type": "github" 1477 | } 1478 | ], 1479 | "time": "2024-07-03T05:10:34+00:00" 1480 | }, 1481 | { 1482 | "name": "sebastian/type", 1483 | "version": "5.1.0", 1484 | "source": { 1485 | "type": "git", 1486 | "url": "https://github.com/sebastianbergmann/type.git", 1487 | "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" 1488 | }, 1489 | "dist": { 1490 | "type": "zip", 1491 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", 1492 | "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", 1493 | "shasum": "" 1494 | }, 1495 | "require": { 1496 | "php": ">=8.2" 1497 | }, 1498 | "require-dev": { 1499 | "phpunit/phpunit": "^11.3" 1500 | }, 1501 | "type": "library", 1502 | "extra": { 1503 | "branch-alias": { 1504 | "dev-main": "5.1-dev" 1505 | } 1506 | }, 1507 | "autoload": { 1508 | "classmap": [ 1509 | "src/" 1510 | ] 1511 | }, 1512 | "notification-url": "https://packagist.org/downloads/", 1513 | "license": [ 1514 | "BSD-3-Clause" 1515 | ], 1516 | "authors": [ 1517 | { 1518 | "name": "Sebastian Bergmann", 1519 | "email": "sebastian@phpunit.de", 1520 | "role": "lead" 1521 | } 1522 | ], 1523 | "description": "Collection of value objects that represent the types of the PHP type system", 1524 | "homepage": "https://github.com/sebastianbergmann/type", 1525 | "support": { 1526 | "issues": "https://github.com/sebastianbergmann/type/issues", 1527 | "security": "https://github.com/sebastianbergmann/type/security/policy", 1528 | "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" 1529 | }, 1530 | "funding": [ 1531 | { 1532 | "url": "https://github.com/sebastianbergmann", 1533 | "type": "github" 1534 | } 1535 | ], 1536 | "time": "2024-09-17T13:12:04+00:00" 1537 | }, 1538 | { 1539 | "name": "sebastian/version", 1540 | "version": "5.0.2", 1541 | "source": { 1542 | "type": "git", 1543 | "url": "https://github.com/sebastianbergmann/version.git", 1544 | "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" 1545 | }, 1546 | "dist": { 1547 | "type": "zip", 1548 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", 1549 | "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", 1550 | "shasum": "" 1551 | }, 1552 | "require": { 1553 | "php": ">=8.2" 1554 | }, 1555 | "type": "library", 1556 | "extra": { 1557 | "branch-alias": { 1558 | "dev-main": "5.0-dev" 1559 | } 1560 | }, 1561 | "autoload": { 1562 | "classmap": [ 1563 | "src/" 1564 | ] 1565 | }, 1566 | "notification-url": "https://packagist.org/downloads/", 1567 | "license": [ 1568 | "BSD-3-Clause" 1569 | ], 1570 | "authors": [ 1571 | { 1572 | "name": "Sebastian Bergmann", 1573 | "email": "sebastian@phpunit.de", 1574 | "role": "lead" 1575 | } 1576 | ], 1577 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1578 | "homepage": "https://github.com/sebastianbergmann/version", 1579 | "support": { 1580 | "issues": "https://github.com/sebastianbergmann/version/issues", 1581 | "security": "https://github.com/sebastianbergmann/version/security/policy", 1582 | "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" 1583 | }, 1584 | "funding": [ 1585 | { 1586 | "url": "https://github.com/sebastianbergmann", 1587 | "type": "github" 1588 | } 1589 | ], 1590 | "time": "2024-10-09T05:16:32+00:00" 1591 | }, 1592 | { 1593 | "name": "theseer/tokenizer", 1594 | "version": "1.2.3", 1595 | "source": { 1596 | "type": "git", 1597 | "url": "https://github.com/theseer/tokenizer.git", 1598 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" 1599 | }, 1600 | "dist": { 1601 | "type": "zip", 1602 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", 1603 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", 1604 | "shasum": "" 1605 | }, 1606 | "require": { 1607 | "ext-dom": "*", 1608 | "ext-tokenizer": "*", 1609 | "ext-xmlwriter": "*", 1610 | "php": "^7.2 || ^8.0" 1611 | }, 1612 | "type": "library", 1613 | "autoload": { 1614 | "classmap": [ 1615 | "src/" 1616 | ] 1617 | }, 1618 | "notification-url": "https://packagist.org/downloads/", 1619 | "license": [ 1620 | "BSD-3-Clause" 1621 | ], 1622 | "authors": [ 1623 | { 1624 | "name": "Arne Blankerts", 1625 | "email": "arne@blankerts.de", 1626 | "role": "Developer" 1627 | } 1628 | ], 1629 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1630 | "support": { 1631 | "issues": "https://github.com/theseer/tokenizer/issues", 1632 | "source": "https://github.com/theseer/tokenizer/tree/1.2.3" 1633 | }, 1634 | "funding": [ 1635 | { 1636 | "url": "https://github.com/theseer", 1637 | "type": "github" 1638 | } 1639 | ], 1640 | "time": "2024-03-03T12:36:25+00:00" 1641 | } 1642 | ], 1643 | "aliases": [], 1644 | "minimum-stability": "stable", 1645 | "stability-flags": {}, 1646 | "prefer-stable": false, 1647 | "prefer-lowest": false, 1648 | "platform": { 1649 | "php": "8.4.*" 1650 | }, 1651 | "platform-dev": {}, 1652 | "plugin-api-version": "2.6.0" 1653 | } 1654 | -------------------------------------------------------------------------------- /docker-compose.yml.dist: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | server: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | container_name: 'design_patterns_php' 8 | restart: 'on-failure' 9 | tty: true 10 | volumes: 11 | - .:/var/www/html 12 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | src 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Behavioral/ChainOfResponsibility/AbstractValidator.php: -------------------------------------------------------------------------------- 1 | next) { 14 | return true; 15 | } 16 | 17 | return $this->next->validate($data); 18 | } 19 | 20 | public function next(self $next): self 21 | { 22 | $this->next = $next; 23 | 24 | return $next; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Behavioral/ChainOfResponsibility/IsGreaterThan.php: -------------------------------------------------------------------------------- 1 | length) { 16 | return false; 17 | } 18 | 19 | return parent::validate($data); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Behavioral/ChainOfResponsibility/IsLessThan.php: -------------------------------------------------------------------------------- 1 | = $this->length) { 16 | return false; 17 | } 18 | 19 | return parent::validate($data); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Behavioral/ChainOfResponsibility/IsNotNull.php: -------------------------------------------------------------------------------- 1 | next(new IsNotNull()) 27 | ->next(new IsString()) 28 | ->next(new IsLessThan(10)) 29 | ->next(new IsGreaterThan(5)); 30 | 31 | self::assertFalse($chainValidation->validate($isNull)); 32 | self::assertFalse($chainValidation->validate($isNotString)); 33 | self::assertFalse($chainValidation->validate($isNotLessThan10)); 34 | self::assertFalse($chainValidation->validate($isNotGreaterThan5)); 35 | self::assertTrue($chainValidation->validate($isValid)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Behavioral/ChainOfResponsibility/ValidatorChain.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 14 | } 15 | 16 | public function getId(): string 17 | { 18 | return $this->id; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Behavioral/Command/CommandInterface.php: -------------------------------------------------------------------------------- 1 | command = $command; 14 | } 15 | 16 | public function invoke(): void 17 | { 18 | $this->command->execute(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Behavioral/Command/InteractiveInterface.php: -------------------------------------------------------------------------------- 1 | interactive->like($this->author->getId()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Behavioral/Command/Post.php: -------------------------------------------------------------------------------- 1 | shouldUndo($authorId, self::LIKE_SLUG)) { 19 | unset($this->reactionStatistics[$authorId]); 20 | 21 | return; 22 | } 23 | 24 | $this->reactionStatistics[$authorId] = self::LIKE_SLUG; 25 | } 26 | 27 | public function wow(string $authorId): void 28 | { 29 | if ($this->shouldUndo($authorId, self::WOW_SLUG)) { 30 | unset($this->reactionStatistics[$authorId]); 31 | 32 | return; 33 | } 34 | 35 | $this->reactionStatistics[$authorId] = self::WOW_SLUG; 36 | } 37 | 38 | public function countLikes(): int 39 | { 40 | $sums = array_count_values($this->reactionStatistics); 41 | 42 | return $sums[self::LIKE_SLUG] ?? 0; 43 | } 44 | 45 | public function countWows(): int 46 | { 47 | $sums = array_count_values($this->reactionStatistics); 48 | 49 | return $sums[self::WOW_SLUG] ?? 0; 50 | } 51 | 52 | private function shouldUndo(string $authorId, string $reactionSlug): bool 53 | { 54 | return !empty($this->reactionStatistics[$authorId]) 55 | && ($this->reactionStatistics[$authorId] === $reactionSlug); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Behavioral/Command/Test/CommandInvokerTest.php: -------------------------------------------------------------------------------- 1 | createMock(CommandInterface::class); 16 | $invoker = new CommandInvoker(); 17 | $invoker->setCommand($command); 18 | 19 | $command 20 | ->expects($this->once()) 21 | ->method('execute'); 22 | 23 | $invoker->invoke(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Behavioral/Command/Test/LikeTest.php: -------------------------------------------------------------------------------- 1 | execute(); 20 | 21 | self::assertSame(1, $interactive->countLikes()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Behavioral/Command/Test/WowTest.php: -------------------------------------------------------------------------------- 1 | execute(); 20 | 21 | self::assertSame(1, $interactive->countWows()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Behavioral/Command/Wow.php: -------------------------------------------------------------------------------- 1 | interactive->wow($this->author->getId()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Behavioral/Memento/Package.php: -------------------------------------------------------------------------------- 1 | height <= self::SMALL_HEIGHT 24 | && $this->width <= self::SMALL_WIDTH 25 | && $this->length <= self::SMALL_LENGTH 26 | && $this->weight <= self::SMALL_WEIGHT; 27 | } 28 | 29 | public function increaseWeight(float $weight): void 30 | { 31 | $this->weight += $weight; 32 | } 33 | 34 | public function snapshot(): PackageSnapshot 35 | { 36 | return new PackageSnapshot( 37 | $this->width, 38 | $this->height, 39 | $this->length, 40 | $this->weight 41 | ); 42 | } 43 | 44 | public static function restore(PackageSnapshot $snapshot): self 45 | { 46 | return new self( 47 | $snapshot->width, 48 | $snapshot->height, 49 | $snapshot->length, 50 | $snapshot->weight 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Behavioral/Memento/PackageSnapshot.php: -------------------------------------------------------------------------------- 1 | increaseWeight(25.00); 18 | 19 | self::assertSame(50.00, $package->snapshot()->weight); 20 | } 21 | 22 | public function testCanMakeSnapshot(): void 23 | { 24 | $width = 8.00; 25 | $height = 38.00; 26 | $length = 68.00; 27 | $weight = 25.00; 28 | $package = new Package($width, $height, $length, $weight); 29 | 30 | $snapshot = $package->snapshot(); 31 | 32 | self::assertSame($width, $snapshot->width); 33 | self::assertSame($height, $snapshot->height); 34 | self::assertSame($length, $snapshot->length); 35 | self::assertSame($weight, $snapshot->weight); 36 | } 37 | 38 | 39 | public function testCanRestoreFromSnapshot(): void 40 | { 41 | $snapshot = new PackageSnapshot(8.00, 38.00, 68.00, 25.00); 42 | 43 | self::assertInstanceOf(Package::class, Package::restore($snapshot)); 44 | } 45 | 46 | public function testIsSmall(): void 47 | { 48 | $package = new Package(8.00, 38.00, 68.00, 25.00); 49 | 50 | self::assertTrue($package->isSmall()); 51 | } 52 | 53 | public function testIsNotSmallWhenWidthIsToBig(): void 54 | { 55 | $package = new Package(8.01, 1.00, 1.00, 1.00); 56 | 57 | self::assertFalse($package->isSmall()); 58 | } 59 | 60 | public function testIsNotSmallWhenHeightIsToBig(): void 61 | { 62 | $package = new Package(1.00, 38.01, 1.00, 1.00); 63 | 64 | self::assertFalse($package->isSmall()); 65 | } 66 | 67 | public function testIsNotSmallWhenLengthIsToBig(): void 68 | { 69 | $package = new Package(1.00, 1.00, 68.01, 1.00); 70 | 71 | self::assertFalse($package->isSmall()); 72 | } 73 | 74 | public function testIsNotSmallWhenWeightIsToBig(): void 75 | { 76 | $package = new Package(1.00, 1.00, 1.00, 25.01); 77 | 78 | self::assertFalse($package->isSmall()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Behavioral/NullObject/Client.php: -------------------------------------------------------------------------------- 1 | type = $type; 14 | } 15 | 16 | public function getType(): ClientTypeInterface 17 | { 18 | return $this->type ?? new NullClientType(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Behavioral/NullObject/ClientType.php: -------------------------------------------------------------------------------- 1 | level = in_array($level, [self::LEVEL_0, self::LEVEL_1, self::LEVEL_2, self::LEVEL_3]) ? $level : 1; 13 | } 14 | 15 | public function getLevel(): int 16 | { 17 | return $this->level; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Behavioral/NullObject/ClientTypeInterface.php: -------------------------------------------------------------------------------- 1 | setType($clientType); 21 | 22 | self::assertEquals($clientType, $client->getType()); 23 | self::assertInstanceOf(ClientTypeInterface::class, $client->getType()); 24 | } 25 | 26 | public function testCanGetTypeWhenIsNull(): void 27 | { 28 | $client = new Client(); 29 | 30 | self::assertInstanceOf(NullClientType::class, $client->getType()); 31 | self::assertInstanceOf(ClientTypeInterface::class, $client->getType()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/Conference.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 24 | $this->type = Type::UNDECIDED; 25 | $this->observers = new SplObjectStorage(); 26 | } 27 | 28 | public function attach(SplObserver $observer): void 29 | { 30 | $this->observers->attach($observer); 31 | } 32 | 33 | public function detach(SplObserver $observer): void 34 | { 35 | $this->observers->detach($observer); 36 | } 37 | 38 | public function notify(): void 39 | { 40 | /** @var SplObserver $observer */ 41 | foreach ($this->observers as $observer) { 42 | $observer->update($this); 43 | } 44 | } 45 | 46 | public function countObservers(): int 47 | { 48 | return $this->observers->count(); 49 | } 50 | 51 | public function getId(): string 52 | { 53 | return $this->id; 54 | } 55 | 56 | public function getDate(): DateTimeImmutable 57 | { 58 | return $this->date; 59 | } 60 | 61 | public function changeDate(DateTimeImmutable $date): void 62 | { 63 | $this->date = $date; 64 | $this->notify(); 65 | } 66 | 67 | public function isOnline(): bool 68 | { 69 | return Type::ONLINE === $this->type; 70 | } 71 | 72 | public function isOffline(): bool 73 | { 74 | return Type::OFFLINE === $this->type; 75 | } 76 | 77 | /** 78 | * @throws InvalidConferenceTypeException 79 | */ 80 | public function online(): void 81 | { 82 | if (Type::UNDECIDED !== $this->type) { 83 | throw new InvalidConferenceTypeException(); 84 | } 85 | 86 | $this->type = Type::ONLINE; 87 | $this->notify(); 88 | } 89 | 90 | /** 91 | * @throws InvalidConferenceTypeException 92 | */ 93 | public function offline(): void 94 | { 95 | if (Type::UNDECIDED !== $this->type) { 96 | throw new InvalidConferenceTypeException(); 97 | } 98 | 99 | $this->type = Type::OFFLINE; 100 | $this->notify(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/ConferenceStatistic.php: -------------------------------------------------------------------------------- 1 | isOffline()) { 23 | ++$this->offlineAmount; 24 | 25 | return; 26 | } 27 | 28 | if ($subject->isOnline()) { 29 | ++$this->onlineAmount; 30 | } 31 | } 32 | 33 | public function countOnline(): int 34 | { 35 | return $this->onlineAmount; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/InvalidConferenceTypeException.php: -------------------------------------------------------------------------------- 1 | calendar = []; 18 | } 19 | 20 | public function update(SplSubject $subject): void 21 | { 22 | if (! $subject instanceof Conference) { 23 | return; 24 | } 25 | 26 | $conferenceId = $subject->getId(); 27 | $conferenceDate = $subject->getDate(); 28 | 29 | if ($this->isDateFree($conferenceId, $conferenceDate)) { 30 | $this->book($conferenceId, $conferenceDate); 31 | } else { 32 | $this->cancel($conferenceId); 33 | } 34 | } 35 | 36 | public function getCalendar(): array 37 | { 38 | return $this->calendar; 39 | } 40 | 41 | private function book(string $conferenceId, DateTimeImmutable $conferenceDate): void 42 | { 43 | $this->calendar[$conferenceId] = $conferenceDate->format('d.m.Y'); 44 | } 45 | 46 | private function cancel(string $conferenceId): void 47 | { 48 | unset($this->calendar[$conferenceId]); 49 | } 50 | 51 | private function isDateFree(string $conferenceId, DateTimeImmutable $date): bool 52 | { 53 | return !in_array($date->format('d.m.Y'), $this->calendar) 54 | || (isset($this->calendar[$conferenceId]) && $this->calendar[$conferenceId] === $date->format('d.m.Y')); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/Sponsor.php: -------------------------------------------------------------------------------- 1 | isOnline()) { 19 | return; 20 | } 21 | 22 | $subject->detach($this); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/Test/ConferenceStatisticTest.php: -------------------------------------------------------------------------------- 1 | attach($conferenceStatistics); 19 | $conference2 = new Conference(new DateTimeImmutable()); 20 | $conference2->attach($conferenceStatistics); 21 | 22 | $conference1->online(); 23 | $conference2->online(); 24 | 25 | self::assertSame($conferenceStatistics->countOnline(), 2); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/Test/ConferenceTest.php: -------------------------------------------------------------------------------- 1 | createMock(SplObserver::class); 19 | $conference->attach($splObserverMock); 20 | 21 | $splObserverMock 22 | ->expects($this->once()) 23 | ->method('update'); 24 | 25 | $conference->notify(); 26 | } 27 | 28 | public function testCannotNotifyDetachedObserver(): void 29 | { 30 | $conference = new Conference(new DateTimeImmutable()); 31 | $splObserverMock = $this->createMock(SplObserver::class); 32 | $conference->attach($splObserverMock); 33 | $conference->detach($splObserverMock); 34 | 35 | $splObserverMock 36 | ->expects($this->never()) 37 | ->method('update'); 38 | 39 | $conference->notify(); 40 | } 41 | 42 | public function testCanChangeTypeToOnlineIfIsUndecided(): void 43 | { 44 | $conferenceUndecided = new Conference(new DateTimeImmutable()); 45 | 46 | $conferenceUndecided->online(); 47 | 48 | self::assertTrue($conferenceUndecided->isOnline()); 49 | } 50 | 51 | public function testCannotChangeTypeToOnlineIfIsNotUndecided(): void 52 | { 53 | $conferenceOffline = (new Conference(new DateTimeImmutable())); 54 | $conferenceOffline->offline(); 55 | 56 | self::expectException(InvalidConferenceTypeException::class); 57 | 58 | $conferenceOffline->online(); 59 | } 60 | 61 | public function testCannotChangeTypeToOfflineIfIsNotUndecided(): void 62 | { 63 | $conferenceOnline = (new Conference(new DateTimeImmutable())); 64 | $conferenceOnline->online(); 65 | 66 | self::expectException(InvalidConferenceTypeException::class); 67 | 68 | $conferenceOnline->offline(); 69 | } 70 | 71 | public function testCanChangeTypeToOfflineIfIsUndecided(): void 72 | { 73 | $conferenceUndecided = new Conference(new DateTimeImmutable()); 74 | 75 | $conferenceUndecided->offline(); 76 | 77 | self::assertTrue($conferenceUndecided->isOffline()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/Test/ParticipantTest.php: -------------------------------------------------------------------------------- 1 | attach($participant); 19 | 20 | $conference->online(); 21 | 22 | self::assertSame($participant->getCalendar()[$conference->getId()], $conference->getDate()->format('d.m.Y')); 23 | } 24 | 25 | public function testCannotBookSameConferenceTwice(): void 26 | { 27 | $participant = new Participant(); 28 | $conference = new Conference(new DateTimeImmutable()); 29 | $conference->attach($participant); 30 | 31 | $conference->changeDate(new DateTimeImmutable()); 32 | $conference->online(); 33 | 34 | self::assertCount(1, $participant->getCalendar()); 35 | } 36 | 37 | public function testCannotBookSameDateTwice(): void 38 | { 39 | $sameDate = new DateTimeImmutable(); 40 | $participant = new Participant(); 41 | $conference1 = new Conference($sameDate); 42 | $conference1->attach($participant); 43 | $conference2 = new Conference($sameDate); 44 | $conference2->attach($participant); 45 | 46 | $conference1->online(); 47 | $conference2->online(); 48 | 49 | self::assertCount(1, $participant->getCalendar()); 50 | } 51 | 52 | public function testIsBookDateChangedWhenNewDateIsFree(): void 53 | { 54 | $changedDate = new DateTimeImmutable(); 55 | $participant = new Participant(); 56 | $conference = new Conference(new DateTimeImmutable()); 57 | $conference->attach($participant); 58 | 59 | $conference->changeDate($changedDate); 60 | $conference->online(); 61 | 62 | self::assertSame($participant->getCalendar()[$conference->getId()], $changedDate->format('d.m.Y')); 63 | } 64 | 65 | public function testIsBookDateCanceledWhenNewDateIsNotFree(): void 66 | { 67 | $bookedDate = new DateTimeImmutable(); 68 | $participant = new Participant(); 69 | $conference1 = new Conference($bookedDate); 70 | $conference1->attach($participant); 71 | $conference2 = new Conference((new DateTimeImmutable())->modify('+1 days')); 72 | $conference2->attach($participant); 73 | 74 | $conference1->online(); 75 | $conference2->online(); 76 | $conference2->changeDate($bookedDate); 77 | 78 | self::assertCount(1, $participant->getCalendar()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/Test/SponsorTest.php: -------------------------------------------------------------------------------- 1 | attach($sponsor); 19 | 20 | $conference->online(); 21 | 22 | self::assertSame(0, $conference->countObservers()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Behavioral/Observer/Type.php: -------------------------------------------------------------------------------- 1 | new Reopened(), 13 | State::RESOLVED => new Resolved(), 14 | default => throw new InvalidStateException() 15 | }; 16 | } 17 | 18 | public function canEstimate(): bool 19 | { 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Behavioral/State/InProgress.php: -------------------------------------------------------------------------------- 1 | new Resolved(), 13 | State::OPEN => new Open(), 14 | default => throw new InvalidStateException() 15 | }; 16 | } 17 | 18 | public function canEstimate(): bool 19 | { 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Behavioral/State/InvalidStateException.php: -------------------------------------------------------------------------------- 1 | new InProgress(), 13 | default => throw new InvalidStateException() 14 | }; 15 | } 16 | 17 | public function canEstimate(): bool 18 | { 19 | return true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Behavioral/State/Reopened.php: -------------------------------------------------------------------------------- 1 | new InProgress(), 13 | State::CLOSED => new Closed(), 14 | default => throw new InvalidStateException() 15 | }; 16 | } 17 | 18 | public function canEstimate(): bool 19 | { 20 | return true; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Behavioral/State/Resolved.php: -------------------------------------------------------------------------------- 1 | new Closed(), 13 | State::IN_PROGRESS => new InProgress(), 14 | default => throw new InvalidStateException() 15 | }; 16 | } 17 | 18 | public function canEstimate(): bool 19 | { 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Behavioral/State/State.php: -------------------------------------------------------------------------------- 1 | state = new Open(); 15 | } 16 | 17 | /** 18 | * @throws InvalidStateException 19 | */ 20 | public function changeState(State $state): void 21 | { 22 | $this->state = $this->state->move($state); 23 | } 24 | 25 | /** 26 | * @throws InvalidStateException 27 | */ 28 | public function estimate(int $points): void 29 | { 30 | if (! $this->state->canEstimate()) { 31 | throw new InvalidStateException(); 32 | } 33 | 34 | $this->estimatePoints = $points; 35 | } 36 | 37 | public function isOpen(): bool 38 | { 39 | return $this->state instanceof Open; 40 | } 41 | 42 | public function isInProgress(): bool 43 | { 44 | return $this->state instanceof InProgress; 45 | } 46 | 47 | public function isResolved(): bool 48 | { 49 | return $this->state instanceof Resolved; 50 | } 51 | 52 | public function isClosed(): bool 53 | { 54 | return $this->state instanceof Closed; 55 | } 56 | 57 | public function isReopened(): bool 58 | { 59 | return $this->state instanceof Reopened; 60 | } 61 | 62 | public function getEstimatePoints(): int 63 | { 64 | return $this->estimatePoints; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Behavioral/State/Test/TaskTest.php: -------------------------------------------------------------------------------- 1 | changeState(State::IN_PROGRESS); 19 | 20 | self::assertTrue($task->isInProgress()); 21 | } 22 | 23 | public function testCannotChangeOpenTaskToOpen(): void 24 | { 25 | $task = new Task(); 26 | 27 | self::expectException(InvalidStateException::class); 28 | 29 | $task->changeState(State::OPEN); 30 | } 31 | 32 | public function testCannotChangeOpenTaskToResolved(): void 33 | { 34 | $task = new Task(); 35 | 36 | self::expectException(InvalidStateException::class); 37 | 38 | $task->changeState(State::RESOLVED); 39 | } 40 | 41 | public function testCannotChangeOpenTaskToReopened(): void 42 | { 43 | $task = new Task(); 44 | 45 | self::expectException(InvalidStateException::class); 46 | 47 | $task->changeState(State::REOPENED); 48 | } 49 | 50 | public function testCannotChangeOpenTaskToClosed(): void 51 | { 52 | $task = new Task(); 53 | 54 | self::expectException(InvalidStateException::class); 55 | 56 | $task->changeState(State::CLOSED); 57 | } 58 | 59 | public function testCanChangeInProgressTaskToOpen(): void 60 | { 61 | $task = new Task(); 62 | $task->changeState(State::IN_PROGRESS); 63 | 64 | $task->changeState(State::OPEN); 65 | 66 | self::assertTrue($task->isOpen()); 67 | } 68 | 69 | public function testCanChangeInProgressTaskToResolved(): void 70 | { 71 | $task = new Task(); 72 | $task->changeState(State::IN_PROGRESS); 73 | 74 | $task->changeState(State::RESOLVED); 75 | 76 | self::assertTrue($task->isResolved()); 77 | } 78 | 79 | public function testCannotChangeInProgressTaskToInProgress(): void 80 | { 81 | $task = new Task(); 82 | $task->changeState(State::IN_PROGRESS); 83 | 84 | self::expectException(InvalidStateException::class); 85 | 86 | $task->changeState(State::IN_PROGRESS); 87 | } 88 | 89 | public function testCannotChangeInProgressTaskToReopened(): void 90 | { 91 | $task = new Task(); 92 | $task->changeState(State::IN_PROGRESS); 93 | 94 | self::expectException(InvalidStateException::class); 95 | 96 | $task->changeState(State::REOPENED); 97 | } 98 | 99 | public function testCannotChangeInProgressTaskToClosed(): void 100 | { 101 | $task = new Task(); 102 | $task->changeState(State::IN_PROGRESS); 103 | 104 | self::expectException(InvalidStateException::class); 105 | 106 | $task->changeState(State::CLOSED); 107 | } 108 | 109 | public function testCanChangeResolvedTaskToInProgress(): void 110 | { 111 | $task = new Task(); 112 | $task->changeState(State::IN_PROGRESS); 113 | $task->changeState(State::RESOLVED); 114 | 115 | $task->changeState(State::IN_PROGRESS); 116 | 117 | self::assertTrue($task->isInProgress()); 118 | } 119 | 120 | public function testCanChangeResolvedTaskToClosed(): void 121 | { 122 | $task = new Task(); 123 | $task->changeState(State::IN_PROGRESS); 124 | $task->changeState(State::RESOLVED); 125 | 126 | $task->changeState(State::CLOSED); 127 | 128 | self::assertTrue($task->isClosed()); 129 | } 130 | 131 | public function testCannotChangeResolvedTaskToResolved(): void 132 | { 133 | $task = new Task(); 134 | $task->changeState(State::IN_PROGRESS); 135 | $task->changeState(State::RESOLVED); 136 | 137 | self::expectException(InvalidStateException::class); 138 | 139 | $task->changeState(State::RESOLVED); 140 | } 141 | 142 | public function testCannotChangeResolvedTaskToOpen(): void 143 | { 144 | $task = new Task(); 145 | $task->changeState(State::IN_PROGRESS); 146 | $task->changeState(State::RESOLVED); 147 | 148 | self::expectException(InvalidStateException::class); 149 | 150 | $task->changeState(State::OPEN); 151 | } 152 | 153 | public function testCannotChangeResolvedTaskToReopened(): void 154 | { 155 | $task = new Task(); 156 | $task->changeState(State::IN_PROGRESS); 157 | $task->changeState(State::RESOLVED); 158 | 159 | self::expectException(InvalidStateException::class); 160 | 161 | $task->changeState(State::REOPENED); 162 | } 163 | 164 | public function testCanChangeClosedTaskToReopened(): void 165 | { 166 | $task = new Task(); 167 | $task->changeState(State::IN_PROGRESS); 168 | $task->changeState(State::RESOLVED); 169 | $task->changeState(State::CLOSED); 170 | 171 | $task->changeState(State::REOPENED); 172 | 173 | self::assertTrue($task->isReopened()); 174 | } 175 | 176 | public function testCanChangeClosedTaskToResolved(): void 177 | { 178 | $task = new Task(); 179 | $task->changeState(State::IN_PROGRESS); 180 | $task->changeState(State::RESOLVED); 181 | $task->changeState(State::CLOSED); 182 | 183 | $task->changeState(State::RESOLVED); 184 | 185 | self::assertTrue($task->isResolved()); 186 | } 187 | 188 | public function testCannotChangeClosedTaskToClosed(): void 189 | { 190 | $task = new Task(); 191 | $task->changeState(State::IN_PROGRESS); 192 | $task->changeState(State::RESOLVED); 193 | $task->changeState(State::CLOSED); 194 | 195 | self::expectException(InvalidStateException::class); 196 | 197 | $task->changeState(State::CLOSED); 198 | } 199 | 200 | public function testCannotChangeClosedTaskToOpen(): void 201 | { 202 | $task = new Task(); 203 | $task->changeState(State::IN_PROGRESS); 204 | $task->changeState(State::RESOLVED); 205 | $task->changeState(State::CLOSED); 206 | 207 | self::expectException(InvalidStateException::class); 208 | 209 | $task->changeState(State::OPEN); 210 | } 211 | 212 | public function testCannotChangeClosedTaskToInProgress(): void 213 | { 214 | $task = new Task(); 215 | $task->changeState(State::IN_PROGRESS); 216 | $task->changeState(State::RESOLVED); 217 | $task->changeState(State::CLOSED); 218 | 219 | self::expectException(InvalidStateException::class); 220 | 221 | $task->changeState(State::IN_PROGRESS); 222 | } 223 | 224 | public function testCanChangeReopenedTaskToInProgress(): void 225 | { 226 | $task = new Task(); 227 | $task->changeState(State::IN_PROGRESS); 228 | $task->changeState(State::RESOLVED); 229 | $task->changeState(State::CLOSED); 230 | $task->changeState(State::REOPENED); 231 | 232 | $task->changeState(State::IN_PROGRESS); 233 | 234 | self::assertTrue($task->isInProgress()); 235 | } 236 | 237 | public function testCanChangeReopenedTaskToClosed(): void 238 | { 239 | $task = new Task(); 240 | $task->changeState(State::IN_PROGRESS); 241 | $task->changeState(State::RESOLVED); 242 | $task->changeState(State::CLOSED); 243 | $task->changeState(State::REOPENED); 244 | 245 | $task->changeState(State::CLOSED); 246 | 247 | self::assertTrue($task->isClosed()); 248 | } 249 | 250 | public function testCannotChangeReopenedTaskToReopened(): void 251 | { 252 | $task = new Task(); 253 | $task->changeState(State::IN_PROGRESS); 254 | $task->changeState(State::RESOLVED); 255 | $task->changeState(State::CLOSED); 256 | $task->changeState(State::REOPENED); 257 | 258 | self::expectException(InvalidStateException::class); 259 | 260 | $task->changeState(State::REOPENED); 261 | } 262 | 263 | public function testCannotChangeReopenedTaskToResolved(): void 264 | { 265 | $task = new Task(); 266 | $task->changeState(State::IN_PROGRESS); 267 | $task->changeState(State::RESOLVED); 268 | $task->changeState(State::CLOSED); 269 | $task->changeState(State::REOPENED); 270 | 271 | self::expectException(InvalidStateException::class); 272 | 273 | $task->changeState(State::RESOLVED); 274 | } 275 | 276 | public function testCannotChangeReopenedTaskToOpen(): void 277 | { 278 | $task = new Task(); 279 | $task->changeState(State::IN_PROGRESS); 280 | $task->changeState(State::RESOLVED); 281 | $task->changeState(State::CLOSED); 282 | $task->changeState(State::REOPENED); 283 | 284 | self::expectException(InvalidStateException::class); 285 | 286 | $task->changeState(State::OPEN); 287 | } 288 | 289 | public function testCanEstimateOpenTask(): void 290 | { 291 | $points = 5; 292 | $task = new Task(); 293 | 294 | $task->estimate($points); 295 | 296 | self::assertSame($points, $task->getEstimatePoints()); 297 | } 298 | 299 | public function testCanEstimateReopenedTask(): void 300 | { 301 | $points = 5; 302 | $task = new Task(); 303 | $task->changeState(State::IN_PROGRESS); 304 | $task->changeState(State::RESOLVED); 305 | $task->changeState(State::CLOSED); 306 | $task->changeState(State::REOPENED); 307 | 308 | $task->estimate($points); 309 | 310 | self::assertSame($points, $task->getEstimatePoints()); 311 | } 312 | 313 | public function testCannotEstimateInProgressTask(): void 314 | { 315 | $points = 5; 316 | $task = new Task(); 317 | $task->changeState(State::IN_PROGRESS); 318 | 319 | self::expectException(InvalidStateException::class); 320 | 321 | $task->estimate($points); 322 | } 323 | 324 | public function testCannotEstimateResolvedTask(): void 325 | { 326 | $points = 5; 327 | $task = new Task(); 328 | $task->changeState(State::IN_PROGRESS); 329 | $task->changeState(State::RESOLVED); 330 | 331 | self::expectException(InvalidStateException::class); 332 | 333 | $task->estimate($points); 334 | } 335 | 336 | public function testCannotEstimateClosedTask(): void 337 | { 338 | $points = 5; 339 | $task = new Task(); 340 | $task->changeState(State::IN_PROGRESS); 341 | $task->changeState(State::RESOLVED); 342 | $task->changeState(State::CLOSED); 343 | 344 | self::expectException(InvalidStateException::class); 345 | 346 | $task->estimate($points); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/Behavioral/Strategy/EmailNotifier.php: -------------------------------------------------------------------------------- 1 | email; 17 | } 18 | 19 | public function shouldNotifyBySystem(): bool 20 | { 21 | return $this->system; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Behavioral/Strategy/NotifyInterface.php: -------------------------------------------------------------------------------- 1 | onPasswordExpired($userNotificationPreference); 19 | 20 | self::assertCount(1, $messages); 21 | self::assertContains(sprintf('Email: %s', UserNotification::PASSWORD_EXPIRE_MESSAGE), $messages); 22 | } 23 | 24 | public function testCanNotifyUserBySystemAboutPasswordExpire(): void 25 | { 26 | $userNotificationPreference = new NotificationPreference(false, true); 27 | $userNotification = new UserNotification(); 28 | 29 | $messages = $userNotification->onPasswordExpired($userNotificationPreference); 30 | 31 | self::assertCount(1, $messages); 32 | self::assertContains(sprintf('System: %s', UserNotification::PASSWORD_EXPIRE_MESSAGE), $messages); 33 | } 34 | 35 | public function testCanNotifyUserByEmailAndSystemAboutPasswordExpire(): void 36 | { 37 | $userNotificationPreference = new NotificationPreference(true, true); 38 | $userNotification = new UserNotification(); 39 | 40 | $messages = $userNotification->onPasswordExpired($userNotificationPreference); 41 | 42 | self::assertCount(2, $messages); 43 | self::assertContains(sprintf('Email: %s', UserNotification::PASSWORD_EXPIRE_MESSAGE), $messages); 44 | self::assertContains(sprintf('System: %s', UserNotification::PASSWORD_EXPIRE_MESSAGE), $messages); 45 | } 46 | 47 | public function testCannotNotifyUserWithoutPreferenceAboutPasswordExpire(): void 48 | { 49 | $userNotificationPreference = new NotificationPreference(false, false); 50 | $userNotification = new UserNotification(); 51 | 52 | $messages = $userNotification->onPasswordExpired($userNotificationPreference); 53 | 54 | self::assertCount(0, $messages); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Behavioral/Strategy/UserNotification.php: -------------------------------------------------------------------------------- 1 | determineStrategies($notificationPreference); 15 | /** @var NotifyInterface $notifyStrategy */ 16 | foreach ($notifyStrategies as $notifyStrategy) { 17 | $messages[] = $notifyStrategy->notify(self::PASSWORD_EXPIRE_MESSAGE); 18 | } 19 | 20 | return $messages; 21 | } 22 | 23 | private function determineStrategies(NotificationPreference $notificationPreference): array 24 | { 25 | $notifyStrategies = []; 26 | 27 | if ($notificationPreference->shouldNotifyByEmail()) { 28 | $notifyStrategies[] = new EmailNotifier(); 29 | } 30 | 31 | if ($notificationPreference->shouldNotifyBySystem()) { 32 | $notifyStrategies[] = new SystemNotifier(); 33 | } 34 | 35 | return $notifyStrategies; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Behavioral/TemplateMethod/AbstractFileLogSynchronizer.php: -------------------------------------------------------------------------------- 1 | readFromFile( 18 | $this->getFilePath($fileName) 19 | ); 20 | $logs = $this->transform($unprocessedLogs); 21 | $this->logRepository->save($logs); 22 | } 23 | 24 | abstract protected function readFromFile(string $filePath): array; 25 | 26 | abstract protected function transform(array $unprocessedLogs): array; 27 | 28 | private function getFilePath(string $fileName): string 29 | { 30 | return self::FILE_PATH . $fileName; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Behavioral/TemplateMethod/CsvFileLogSynchronizer.php: -------------------------------------------------------------------------------- 1 | synchronize('test_logs.csv'); 19 | } catch (BadMethodCallException) { 20 | self::fail('Synchronize action using test_logs.csv does not work.'); 21 | } 22 | 23 | self::assertTrue(true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Behavioral/TemplateMethod/Test/FakeLogRepository.php: -------------------------------------------------------------------------------- 1 | synchronize('test_logs.txt'); 19 | } catch (BadMethodCallException) { 20 | self::fail('Synchronize action using test_logs.txt does not work.'); 21 | } 22 | 23 | self::assertTrue(true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Behavioral/TemplateMethod/TxtFileLogSynchronizer.php: -------------------------------------------------------------------------------- 1 | $unprocessedLog[array_key_first($unprocessedLog)], 19 | 'message' => $unprocessedLog[array_key_last($unprocessedLog)] 20 | ]; 21 | } 22 | 23 | return $unprocessedLogs; 24 | } 25 | 26 | protected function transform(array $unprocessedLogs): array 27 | { 28 | $logs = []; 29 | foreach ($unprocessedLogs as $unprocessedLog) { 30 | $logDate = DateTimeImmutable::createFromFormat('d.m.Y H:i', $unprocessedLog['date']); 31 | if (false === $logDate) { 32 | continue; 33 | } 34 | 35 | $logs[] = new Log($logDate, $unprocessedLog['message']); 36 | } 37 | 38 | return $logs; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Behavioral/TemplateMethod/synchronization/files/test_logs.csv: -------------------------------------------------------------------------------- 1 | "2020-12-30 15:15:15":"Access denied"; 2 | "2020-12-31 18:18:18":"Cannot download report"; 3 | "2020-12-31 18:18:18":"Cannot download report"; 4 | "2020-12-31 22:22:22":"Entity does not exist"; -------------------------------------------------------------------------------- /src/Behavioral/TemplateMethod/synchronization/files/test_logs.txt: -------------------------------------------------------------------------------- 1 | 30.12.2020 22:22, Entity does not exist 2 | 30.12.2020 10:10, Cannot download report 3 | 31.12.2020 12:12, Access denied 4 | 01.01.2021 03:03, Entity does not exist -------------------------------------------------------------------------------- /src/Behavioral/Visitor/LastMinuteCalculator.php: -------------------------------------------------------------------------------- 1 | price * (100 - $participant->getPercentDiscount()) * 0.01; 18 | } 19 | 20 | public function calculateForSpeaker(Speaker $speaker): float 21 | { 22 | return 0.00; 23 | } 24 | 25 | public function calculateForSponsor(Sponsor $sponsor): float 26 | { 27 | return match ($sponsor->getStatus()) { 28 | Status::STATUS_BRONZE => 5 * ($sponsor->withExhibition() ? (self::EXHIBITION_PRICE + $this->price) : $this->price), 29 | Status::STATUS_SILVER => 10 * ($sponsor->withExhibition() ? (self::EXHIBITION_PRICE + $this->price) : $this->price), 30 | Status::STATUS_GOLDEN => $sponsor->withExhibition() ? 200000.00 : 150000.00 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Behavioral/Visitor/Participant.php: -------------------------------------------------------------------------------- 1 | calculateForParticipant($this); 16 | } 17 | 18 | public function getPercentDiscount(): int 19 | { 20 | return $this->isVIP ? 10 : 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Behavioral/Visitor/PriceCalculatorInterface.php: -------------------------------------------------------------------------------- 1 | calculateForSpeaker($this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Behavioral/Visitor/Sponsor.php: -------------------------------------------------------------------------------- 1 | calculateForSponsor($this); 17 | } 18 | 19 | public function getStatus(): Status 20 | { 21 | return $this->status; 22 | } 23 | 24 | public function withExhibition(): bool 25 | { 26 | return $this->withExhibition; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Behavioral/Visitor/Status.php: -------------------------------------------------------------------------------- 1 | calculatePrice(new LastMinuteCalculator(500.00))); 18 | } 19 | 20 | public function testCanCalculateLastMinutePriceForVipParticipant(): void 21 | { 22 | $participant = new Participant(true); 23 | 24 | self::assertSame(450.00, $participant->calculatePrice(new LastMinuteCalculator(500.00))); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Behavioral/Visitor/Test/SpeakerTest.php: -------------------------------------------------------------------------------- 1 | calculatePrice(new LastMinuteCalculator(500.00))); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Behavioral/Visitor/Test/SponsorTest.php: -------------------------------------------------------------------------------- 1 | calculatePrice(new LastMinuteCalculator(500.00))); 19 | } 20 | 21 | public function testCanCalculateLastMinutePriceForBronzeSponsorWithExhibition(): void 22 | { 23 | $sponsor = new Sponsor(Status::STATUS_BRONZE, true); 24 | 25 | self::assertSame(12500.00, $sponsor->calculatePrice(new LastMinuteCalculator(500.00))); 26 | } 27 | 28 | public function testCanCalculateLastMinutePriceForSilverSponsor(): void 29 | { 30 | $sponsor = new Sponsor(Status::STATUS_SILVER, false); 31 | 32 | self::assertSame(5000.00, $sponsor->calculatePrice(new LastMinuteCalculator(500.00))); 33 | } 34 | 35 | public function testCanCalculateLastMinutePriceForSilverSponsorWithExhibition(): void 36 | { 37 | $sponsor = new Sponsor(Status::STATUS_SILVER, true); 38 | 39 | self::assertSame(25000.00, $sponsor->calculatePrice(new LastMinuteCalculator(500.00))); 40 | } 41 | 42 | public function testCanCalculateLastMinutePriceForGoldenSponsor(): void 43 | { 44 | $sponsor = new Sponsor(Status::STATUS_GOLDEN, false); 45 | 46 | self::assertSame(150000.00, $sponsor->calculatePrice(new LastMinuteCalculator(500.00))); 47 | } 48 | 49 | public function testCanCalculateLastMinutePriceForGoldenSponsorWithExhibition(): void 50 | { 51 | $sponsor = new Sponsor(Status::STATUS_GOLDEN, true); 52 | 53 | self::assertSame(200000.00, $sponsor->calculatePrice(new LastMinuteCalculator(500.00))); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Behavioral/Visitor/VisitableByCalculatorInterface.php: -------------------------------------------------------------------------------- 1 | createBreakfast(); 19 | 20 | self::assertInstanceOf(VeganBreakfast::class, $meal); 21 | self::assertInstanceOf(BreakfastInterface::class, $meal); 22 | } 23 | 24 | public function testCanCreateVeganDinner(): void 25 | { 26 | $meal = (new VeganMealFactory())->createDinner(); 27 | 28 | self::assertInstanceOf(VeganDinner::class, $meal); 29 | self::assertInstanceOf(DinnerInterface::class, $meal); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Creational/AbstractFactory/Test/VegetarianMealFactoryTest.php: -------------------------------------------------------------------------------- 1 | createBreakfast(); 19 | 20 | self::assertInstanceOf(VegetarianBreakfast::class, $meal); 21 | self::assertInstanceOf(BreakfastInterface::class, $meal); 22 | } 23 | 24 | public function testCanCreateVegetarianDinner(): void 25 | { 26 | $meal = (new VegetarianMealFactory())->createDinner(); 27 | 28 | self::assertInstanceOf(VegetarianDinner::class, $meal); 29 | self::assertInstanceOf(DinnerInterface::class, $meal); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Creational/AbstractFactory/VeganBreakfast.php: -------------------------------------------------------------------------------- 1 | title = $title; 18 | } 19 | 20 | public function setSalary(int $salary): void 21 | { 22 | $this->salary = $salary; 23 | } 24 | 25 | public function setContactDetails(string $contactDetails): void 26 | { 27 | $this->contactDetails = $contactDetails; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Creational/Builder/AgreementBuilderInterface.php: -------------------------------------------------------------------------------- 1 | builder = $builder; 14 | } 15 | 16 | public function changeBuilder(AgreementBuilderInterface $builder): void 17 | { 18 | $this->builder = $builder; 19 | } 20 | 21 | public function buildAnonymousAgreement(int $salary): void 22 | { 23 | $this->builder 24 | ->addHeader() 25 | ->addSalary($salary); 26 | } 27 | 28 | public function buildFullDetailsAgreement(int $salary, string $contactDetails): void 29 | { 30 | $this->builder 31 | ->addHeader() 32 | ->addSalary($salary) 33 | ->addContactDetails($contactDetails); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Creational/Builder/B2BContract.php: -------------------------------------------------------------------------------- 1 | b2BContract = new B2BContract(); 14 | } 15 | 16 | public function addHeader(): AgreementBuilderInterface 17 | { 18 | $this->b2BContract->setTitle('Services Contract'); 19 | 20 | return $this; 21 | } 22 | 23 | public function addSalary(int $salary): AgreementBuilderInterface 24 | { 25 | $this->b2BContract->setSalary($salary); 26 | 27 | return $this; 28 | } 29 | 30 | public function addContactDetails(string $contactDetails): AgreementBuilderInterface 31 | { 32 | $this->b2BContract->setContactDetails(sprintf('Company: %s', $contactDetails)); 33 | 34 | return $this; 35 | } 36 | 37 | public function getAgreement(): AbstractAgreement 38 | { 39 | return $this->b2BContract; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Creational/Builder/EmploymentContract.php: -------------------------------------------------------------------------------- 1 | employmentContract = new EmploymentContract(); 14 | } 15 | 16 | public function addHeader(): AgreementBuilderInterface 17 | { 18 | $this->employmentContract->setTitle('Employment Contract'); 19 | 20 | return $this; 21 | } 22 | 23 | public function addSalary(int $salary): AgreementBuilderInterface 24 | { 25 | $this->employmentContract->setSalary($salary); 26 | 27 | return $this; 28 | } 29 | 30 | public function addContactDetails(string $contactDetails): AgreementBuilderInterface 31 | { 32 | $this->employmentContract->setContactDetails(sprintf('Person: %s', $contactDetails)); 33 | 34 | return $this; 35 | } 36 | 37 | public function getAgreement(): AbstractAgreement 38 | { 39 | return $this->employmentContract; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Creational/Builder/Test/AgreementDirectorTest.php: -------------------------------------------------------------------------------- 1 | buildAnonymousAgreement(6000); 22 | 23 | self::assertInstanceOf(B2BContract::class, $b2bContractBuilder->getAgreement()); 24 | } 25 | 26 | public function testCanBuildFullDetailsB2BContract(): void 27 | { 28 | $b2bContractBuilder = new B2BContractBuilder(); 29 | $director = new AgreementDirector($b2bContractBuilder); 30 | 31 | $director->buildFullDetailsAgreement(6000, 'Test Company Ltc.'); 32 | 33 | self::assertInstanceOf(B2BContract::class, $b2bContractBuilder->getAgreement()); 34 | } 35 | 36 | public function testCanBuildAnonymousEmploymentContract(): void 37 | { 38 | $employmentContractBuilder = new EmploymentContractBuilder(); 39 | $director = new AgreementDirector($employmentContractBuilder); 40 | 41 | $director->buildAnonymousAgreement(5000); 42 | 43 | self::assertInstanceOf(EmploymentContract::class, $employmentContractBuilder->getAgreement()); 44 | } 45 | 46 | public function testCanBuildFullDetailsEmploymentContract(): void 47 | { 48 | $employmentContractBuilder = new EmploymentContractBuilder(); 49 | $director = new AgreementDirector($employmentContractBuilder); 50 | 51 | $director->buildFullDetailsAgreement(5000, 'Jane Doe'); 52 | 53 | self::assertInstanceOf(EmploymentContract::class, $employmentContractBuilder->getAgreement()); 54 | } 55 | 56 | public function testCanSwitchBuilder(): void 57 | { 58 | $b2bContractBuilder = new B2BContractBuilder(); 59 | $director = new AgreementDirector($b2bContractBuilder); 60 | $director->buildAnonymousAgreement(6000); 61 | $employmentContractBuilder = new EmploymentContractBuilder(); 62 | 63 | $director->changeBuilder($employmentContractBuilder); 64 | $director->buildAnonymousAgreement(5000); 65 | 66 | self::assertInstanceOf(EmploymentContract::class, $employmentContractBuilder->getAgreement()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Creational/FactoryMethod/MealFactoryInterface.php: -------------------------------------------------------------------------------- 1 | createMeal(); 17 | 18 | self::assertInstanceOf(MealInterface::class, $meal); 19 | self::assertInstanceOf(VeganMeal::class, $meal); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Creational/FactoryMethod/Test/VegetarianMealFactoryTest.php: -------------------------------------------------------------------------------- 1 | createMeal(); 17 | 18 | self::assertInstanceOf(MealInterface::class, $meal); 19 | self::assertInstanceOf(VegetarianMeal::class, $meal); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Creational/FactoryMethod/VeganMeal.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Creational/Prototype/Event.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 25 | $place->setEvent($this); 26 | $this->created = new DateTimeImmutable(); 27 | $this->invitations = []; 28 | } 29 | 30 | public function __clone() 31 | { 32 | $this->id = uniqid(); 33 | $this->created = new DateTimeImmutable(); 34 | $this->place = clone $this->place; 35 | $this->place->setEvent($this); 36 | $this->invitations = []; 37 | } 38 | 39 | public function getId(): string 40 | { 41 | return $this->id; 42 | } 43 | 44 | public function getCity(): City 45 | { 46 | return $this->city; 47 | } 48 | 49 | public function getPlace(): Place 50 | { 51 | return $this->place; 52 | } 53 | 54 | public function getBudget(): int 55 | { 56 | return $this->budget; 57 | } 58 | 59 | public function setBudget(int $budget): Event 60 | { 61 | $this->budget = $budget; 62 | 63 | return $this; 64 | } 65 | 66 | public function getCreated(): DateTimeInterface 67 | { 68 | return $this->created; 69 | } 70 | 71 | public function getInvitations(): array 72 | { 73 | return $this->invitations; 74 | } 75 | 76 | public function addInvitation(Invitation $invitation): Event 77 | { 78 | $this->invitations[$invitation->getId()] = $invitation; 79 | 80 | return $this; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Creational/Prototype/EventPrototypeInterface.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 14 | } 15 | 16 | public function getId(): string 17 | { 18 | return $this->id; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Creational/Prototype/Place.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 16 | } 17 | 18 | public function setEvent(?Event $event): void 19 | { 20 | $this->event = $event; 21 | } 22 | 23 | public function getEvent(): ?Event 24 | { 25 | return $this->event; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Creational/Prototype/Test/EventTest.php: -------------------------------------------------------------------------------- 1 | setBudget(1000000) 21 | ->addInvitation(new Invitation()) 22 | ->addInvitation(new Invitation()); 23 | 24 | self::assertInstanceOf(EventPrototypeInterface::class, $event); 25 | self::assertIsString($event->getId()); 26 | self::assertInstanceOf(City::class, $event->getCity()); 27 | self::assertInstanceOf(Place::class, $event->getPlace()); 28 | self::assertSame(1000000, $event->getBudget()); 29 | self::assertInstanceOf(DateTimeInterface::class, $event->getCreated()); 30 | self::assertSame($event, $event->getPlace()->getEvent()); 31 | self::assertCount(2, $event->getInvitations()); 32 | } 33 | 34 | public function testIsConfiguredFineAfterClone(): void 35 | { 36 | $event = (new Event(new City(), new Place())) 37 | ->setBudget(1000000) 38 | ->addInvitation(new Invitation()) 39 | ->addInvitation(new Invitation()); 40 | 41 | $cloneEvent = clone $event; 42 | 43 | self::assertNotEquals($event, $cloneEvent); 44 | self::assertNotSame($event->getId(), $cloneEvent->getId()); 45 | self::assertSame($event->getCity(), $cloneEvent->getCity()); 46 | self::assertSame($event->getBudget(), $cloneEvent->getBudget()); 47 | self::assertNotSame($event->getCreated(), $cloneEvent->getCreated()); 48 | self::assertNotSame($event->getPlace(), $cloneEvent->getPlace()); 49 | self::assertNotSame($event, $cloneEvent->getPlace()->getEvent()); 50 | self::assertEmpty($cloneEvent->getInvitations()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Creational/SimpleFactory/MealFactory.php: -------------------------------------------------------------------------------- 1 | new VegetarianMeal(), 13 | MealType::VEGAN => new VeganMeal() 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Creational/SimpleFactory/MealInterface.php: -------------------------------------------------------------------------------- 1 | create(MealType::VEGETARIAN); 19 | 20 | self::assertInstanceOf(MealInterface::class, $meal); 21 | self::assertInstanceOf(VegetarianMeal::class, $meal); 22 | } 23 | 24 | public function testCanCreateVeganMeal(): void 25 | { 26 | $meal = (new MealFactory())->create(MealType::VEGAN); 27 | 28 | self::assertInstanceOf(MealInterface::class, $meal); 29 | self::assertInstanceOf(VeganMeal::class, $meal); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Creational/SimpleFactory/VeganMeal.php: -------------------------------------------------------------------------------- 1 | getConstructor()->isPublic()); 19 | } 20 | 21 | public function testIfOnlyOneInstanceExists(): void 22 | { 23 | $config = Config::getInstance(); 24 | $secondConfig = Config::getInstance(); 25 | 26 | self::assertSame($config, $secondConfig); 27 | } 28 | 29 | public function testCannotCloneConfig(): void 30 | { 31 | $config = Config::getInstance(); 32 | 33 | self::expectException(Exception::class); 34 | 35 | clone $config; 36 | } 37 | 38 | public function testCannotDeserializeConfig(): void 39 | { 40 | $config = Config::getInstance(); 41 | 42 | self::expectException(Exception::class); 43 | 44 | unserialize(serialize($config)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Structural/Adapter/ExternalLogger.php: -------------------------------------------------------------------------------- 1 | createLogMessage($log), FILE_APPEND); 18 | } 19 | 20 | private function createLogMessage(string $message): string 21 | { 22 | return sprintf("%s: '%s'\n", (new DateTimeImmutable())->format('H:i'), $message); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Structural/Adapter/LogAdapter.php: -------------------------------------------------------------------------------- 1 | logger->saveLogIntoFile($logMessage); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Structural/Adapter/LoggerInterface.php: -------------------------------------------------------------------------------- 1 | createMock(ExternalLogger::class); 25 | $logAdapter = new LogAdapter($externalLogger); 26 | 27 | $externalLogger 28 | ->expects($this->once()) 29 | ->method('saveLogIntoFile') 30 | ->with($message); 31 | 32 | $logAdapter->log($message); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Structural/Bridge/AbstractBenefit.php: -------------------------------------------------------------------------------- 1 | level->getAuthorityFactor() * self::MAX_GRANT; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Structural/Bridge/JobLevelInterface.php: -------------------------------------------------------------------------------- 1 | calculateGrant()); 21 | } 22 | 23 | public function testCalculatingGrantForSenior(): void 24 | { 25 | $healthCareSeniorGrant = 840.00; 26 | 27 | $healthCare = new HealthCare(new Senior()); 28 | 29 | self::assertSame($healthCareSeniorGrant, $healthCare->calculateGrant()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Structural/Bridge/Test/TrainingBudgetTest.php: -------------------------------------------------------------------------------- 1 | calculateGrant()); 21 | } 22 | 23 | public function testCalculatingGrantForSenior(): void 24 | { 25 | $trainingBudgetSeniorGrant = 700.00; 26 | 27 | $trainingBudget = new TrainingBudget(new Senior()); 28 | 29 | self::assertSame($trainingBudgetSeniorGrant, $trainingBudget->calculateGrant()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Structural/Bridge/TrainingBudget.php: -------------------------------------------------------------------------------- 1 | level->getAuthorityFactor(); 15 | 16 | $grant = $this->level->getAuthorityFactor() * self::MAX_GRANT; 17 | return $this->bonus($authorityFactor, $grant); 18 | } 19 | 20 | private function bonus(float $authorityFactor, float $grant): float 21 | { 22 | $halfOfMaxAuthority = (JobLevelInterface::MAX_AUTHORITY / 2) / JobLevelInterface::MAX_AUTHORITY; 23 | if ($authorityFactor < $halfOfMaxAuthority) { 24 | return $grant * self::BONUS_RATIO; 25 | } 26 | 27 | return $grant; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Structural/Composite/Directory.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 16 | $this->elements = []; 17 | } 18 | 19 | public function getId(): string 20 | { 21 | return $this->id; 22 | } 23 | 24 | public function getSize(): int 25 | { 26 | $size = 0; 27 | /** @var FileSystemElementInterface $element */ 28 | foreach ($this->elements as $element) { 29 | $size += $element->getSize(); 30 | } 31 | 32 | return $size; 33 | } 34 | 35 | public function addElement(FileSystemElementInterface $element): void 36 | { 37 | $this->elements[$element->getId()] = $element; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Structural/Composite/File.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 15 | } 16 | 17 | public function getId(): string 18 | { 19 | return $this->id; 20 | } 21 | 22 | public function getSize(): int 23 | { 24 | return $this->size; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Structural/Composite/FileSystemElementInterface.php: -------------------------------------------------------------------------------- 1 | getSize()); 16 | } 17 | 18 | public function testCanGetSizeOfDirectoryThatContainsOnlyFiles(): void 19 | { 20 | $directory = new Directory(); 21 | 22 | $directory->addElement(new File(12)); 23 | $directory->addElement(new File(8)); 24 | 25 | self::assertSame(20, $directory->getSize()); 26 | } 27 | 28 | public function testCanGetSizeOfDirectoryThatContainsDirectoriesAndFiles(): void 29 | { 30 | $firstChildDirectory = new Directory(); 31 | $secondChildDirectory = new Directory(); 32 | $parentDirectory = new Directory(); 33 | 34 | $firstChildDirectory->addElement(new File(12)); 35 | $secondChildDirectory->addElement(new File(12)); 36 | $secondChildDirectory->addElement(new File(24)); 37 | $secondChildDirectory->addElement(new File(12)); 38 | $parentDirectory->addElement(new File(16)); 39 | $parentDirectory->addElement(new File(24)); 40 | $parentDirectory->addElement($firstChildDirectory); 41 | $parentDirectory->addElement($secondChildDirectory); 42 | 43 | self::assertSame(100, $parentDirectory->getSize()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Structural/Decorator/AbstractOptionDecorator.php: -------------------------------------------------------------------------------- 1 | option->calculatePrice() + self::PRICE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Structural/Decorator/Catering.php: -------------------------------------------------------------------------------- 1 | option->calculatePrice() + self::PRICE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Structural/Decorator/Mascot.php: -------------------------------------------------------------------------------- 1 | option->calculatePrice() + self::PRICE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Structural/Decorator/OptionInterface.php: -------------------------------------------------------------------------------- 1 | option->calculatePrice() + self::PRICE; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Structural/Decorator/Test/OptionDecoratorTest.php: -------------------------------------------------------------------------------- 1 | calculatePrice() + Mascot::PRICE, $option->calculatePrice()); 22 | } 23 | 24 | public function testCanDecorateWithAccommodation(): void 25 | { 26 | $ticket = new Ticket(); 27 | $option = new Accommodation($ticket); 28 | 29 | self::assertSame($ticket->calculatePrice() + Accommodation::PRICE, $option->calculatePrice()); 30 | } 31 | 32 | public function testCanDecorateWithSticker(): void 33 | { 34 | $ticket = new Ticket(); 35 | $option = new Sticker($ticket); 36 | 37 | self::assertSame($ticket->calculatePrice() + Sticker::PRICE, $option->calculatePrice()); 38 | } 39 | 40 | public function testCanDecorateWithCatering(): void 41 | { 42 | $ticket = new Ticket(); 43 | $option = new Catering($ticket); 44 | 45 | self::assertSame($ticket->calculatePrice() + Catering::PRICE, $option->calculatePrice()); 46 | } 47 | 48 | public function testCanDecorateFullOption(): void 49 | { 50 | $ticket = new Ticket(); 51 | $option = new Mascot($ticket); 52 | $option = new Accommodation($option); 53 | $option = new Sticker($option); 54 | $option = new Catering($option); 55 | $fullPrice = $ticket->calculatePrice() 56 | + Mascot::PRICE 57 | + Accommodation::PRICE 58 | + Sticker::PRICE + Catering::PRICE; 59 | 60 | self::assertSame($fullPrice, $option->calculatePrice()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Structural/Decorator/Ticket.php: -------------------------------------------------------------------------------- 1 | cloudClient->hasAccount($email)) { 17 | throw new UserNotFoundException(); 18 | } 19 | 20 | return sprintf('Email (Cloud): %s', $email); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Structural/Facade/EmailModule/CloudClientInterface.php: -------------------------------------------------------------------------------- 1 | hasPhoneNumber()) { 23 | return $this->smsNotifier->notify($user->getPhoneNumber()); 24 | } 25 | 26 | try { 27 | return $this->cloudEmailSender->send($user->getEmail()); 28 | } catch (UserNotFoundException) { 29 | return $this->smtpEmailSender->send($user->getEmail()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Structural/Facade/SmsModule/SmsNotifier.php: -------------------------------------------------------------------------------- 1 | setPhoneNumber($phoneNumber); 22 | $notificationFacade = new NotificationFacade( 23 | new CloudApiSender($this->createMock(CloudClientInterface::class)), 24 | new SmtpSender(), 25 | new SmsNotifier() 26 | ); 27 | 28 | self::assertSame($notificationFacade->send($user), "SMS: $phoneNumber"); 29 | } 30 | 31 | public function testCanSendEmailByCloudIfUserHasAccount(): void 32 | { 33 | $email = 'test@koddlo.pl'; 34 | $user = new User($email); 35 | $cloudClient = $this->createMock(CloudClientInterface::class); 36 | $cloudClient 37 | ->method('hasAccount') 38 | ->with($email) 39 | ->willReturn(true); 40 | $notificationFacade = new NotificationFacade( 41 | new CloudApiSender($cloudClient), 42 | new SmtpSender(), 43 | new SmsNotifier() 44 | ); 45 | 46 | self::assertSame($notificationFacade->send($user), "Email (Cloud): $email"); 47 | } 48 | 49 | public function testIsEmailSendBySmtpIfUserDoesNotHaveCloudAccount(): void 50 | { 51 | $email = 'test@koddlo.pl'; 52 | $user = new User($email); 53 | $cloudClient = $this->createMock(CloudClientInterface::class); 54 | $cloudClient 55 | ->method('hasAccount') 56 | ->with($email) 57 | ->willReturn(false); 58 | $notificationFacade = new NotificationFacade( 59 | new CloudApiSender($cloudClient), 60 | new SmtpSender(), 61 | new SmsNotifier() 62 | ); 63 | 64 | self::assertSame($notificationFacade->send($user), "Email (SMTP): $email"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Structural/Facade/User.php: -------------------------------------------------------------------------------- 1 | phoneNumber = $phoneNumber; 18 | } 19 | 20 | public function getEmail(): string 21 | { 22 | return $this->email; 23 | } 24 | 25 | public function hasPhoneNumber(): bool 26 | { 27 | return null !== $this->phoneNumber; 28 | } 29 | 30 | public function getPhoneNumber(): ?string 31 | { 32 | return $this->phoneNumber; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Structural/Iterator/Directory.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 16 | $this->elements = new FileSystemElementCollection(); 17 | } 18 | 19 | public function getId(): string 20 | { 21 | return $this->id; 22 | } 23 | 24 | public function getSize(): int 25 | { 26 | $size = 0; 27 | foreach ($this->elements as $element) { 28 | $size += $element->getSize(); 29 | } 30 | 31 | return $size; 32 | } 33 | 34 | public function addElement(FileSystemElementInterface $element): void 35 | { 36 | $this->elements->add($element); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Structural/Iterator/File.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 15 | } 16 | 17 | public function getId(): string 18 | { 19 | return $this->id; 20 | } 21 | 22 | public function getSize(): int 23 | { 24 | return $this->size; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Structural/Iterator/FileSystemElementCollection.php: -------------------------------------------------------------------------------- 1 | elements[$element->getId()] = $element; 16 | } 17 | 18 | public function current(): ?FileSystemElementInterface 19 | { 20 | return current($this->elements) ?: null; 21 | } 22 | 23 | public function next(): void 24 | { 25 | next($this->elements); 26 | } 27 | 28 | public function key(): ?string 29 | { 30 | return key($this->elements); 31 | } 32 | 33 | public function valid(): bool 34 | { 35 | return null !== $this->key(); 36 | } 37 | 38 | public function rewind(): void 39 | { 40 | reset($this->elements); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Structural/Iterator/FileSystemElementInterface.php: -------------------------------------------------------------------------------- 1 | getSize()); 16 | } 17 | 18 | public function testCanGetSizeOfDirectoryThatContainsOnlyFiles(): void 19 | { 20 | $directory = new Directory(); 21 | 22 | $directory->addElement(new File(12)); 23 | $directory->addElement(new File(8)); 24 | 25 | self::assertSame(20, $directory->getSize()); 26 | } 27 | 28 | public function testCanGetSizeOfDirectoryThatContainsDirectoriesAndFiles(): void 29 | { 30 | $firstChildDirectory = new Directory(); 31 | $secondChildDirectory = new Directory(); 32 | $parentDirectory = new Directory(); 33 | 34 | $firstChildDirectory->addElement(new File(12)); 35 | $secondChildDirectory->addElement(new File(12)); 36 | $secondChildDirectory->addElement(new File(24)); 37 | $secondChildDirectory->addElement(new File(12)); 38 | $parentDirectory->addElement(new File(16)); 39 | $parentDirectory->addElement(new File(24)); 40 | $parentDirectory->addElement($firstChildDirectory); 41 | $parentDirectory->addElement($secondChildDirectory); 42 | 43 | self::assertSame(100, $parentDirectory->getSize()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Structural/Iterator/Test/FileSystemElementCollectionTest.php: -------------------------------------------------------------------------------- 1 | current()); 16 | } 17 | 18 | public function testCanGoForNextElementOfCollection(): void 19 | { 20 | $directoryOne = new Directory(); 21 | $directoryTwo = new Directory(); 22 | $collection = new FileSystemElementCollection(); 23 | $collection->add($directoryOne); 24 | $collection->add($directoryTwo); 25 | 26 | $collection->next(); 27 | 28 | self::assertSame($directoryTwo, $collection->current()); 29 | } 30 | 31 | 32 | public function testIsCurrentKeyElementNullOfEmptyCollection(): void 33 | { 34 | self::assertNull((new FileSystemElementCollection())->key()); 35 | } 36 | 37 | public function testIsCurrentPositionOfEmptyCollectionInvalid(): void 38 | { 39 | self::assertFalse((new FileSystemElementCollection())->valid()); 40 | } 41 | 42 | public function testIsCurrentPositionOfFullCollectionValid(): void 43 | { 44 | $collection = new FileSystemElementCollection(); 45 | $collection->add(new Directory()); 46 | 47 | self::assertTrue($collection->valid()); 48 | } 49 | 50 | public function testCanRewindEmptyCollection(): void 51 | { 52 | $collection = new FileSystemElementCollection(); 53 | 54 | $collection->rewind(); 55 | 56 | self::assertFalse($collection->valid()); 57 | } 58 | 59 | public function testCanRewindFullCollection(): void 60 | { 61 | $directoryOne = new Directory(); 62 | $directoryTwo = new Directory(); 63 | $collection = new FileSystemElementCollection(); 64 | $collection->add($directoryOne); 65 | $collection->add($directoryTwo); 66 | 67 | $collection->next(); 68 | $collection->rewind(); 69 | 70 | self::assertSame($directoryOne, $collection->current()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Structural/Proxy/Base64EncodingInterface.php: -------------------------------------------------------------------------------- 1 | cache[$path])) { 18 | $this->cache[$path] = $this->fileEncoder->encodeBase64($path); 19 | } 20 | 21 | return $this->cache[$path]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Structural/Proxy/FileNotFoundException.php: -------------------------------------------------------------------------------- 1 | createMock(FileEncoder::class); 18 | $fileEncoder 19 | ->expects($this->any()) 20 | ->method('encodeBase64') 21 | ->with($fileTestPath) 22 | ->willReturn($base64example); 23 | $fileEncoderProxy = new FileEncoderProxy($fileEncoder); 24 | 25 | self::assertSame($base64example, $fileEncoderProxy->encodeBase64($fileTestPath)); 26 | } 27 | 28 | public function testProxyCacheResult(): void 29 | { 30 | $fileTestPath = 'https://koddlo.pl/images/test.png'; 31 | $base64example = 'S29kZGxv'; 32 | $fileEncoder = $this->createMock(FileEncoder::class); 33 | $fileEncoder 34 | ->expects($this->once()) 35 | ->method('encodeBase64') 36 | ->with($fileTestPath) 37 | ->willReturn($base64example); 38 | $fileEncoderProxy = new FileEncoderProxy($fileEncoder); 39 | $fileEncoderProxy->encodeBase64($fileTestPath); 40 | $fileEncoderProxy->encodeBase64($fileTestPath); 41 | $fileEncoderProxy->encodeBase64($fileTestPath); 42 | 43 | self::assertSame($base64example, $fileEncoderProxy->encodeBase64($fileTestPath)); 44 | } 45 | } 46 | --------------------------------------------------------------------------------