{"id":33967186,"url":"https://github.com/inklabs/kommerce-core","last_synced_at":"2025-12-12T23:39:28.998Z","repository":{"id":56991576,"uuid":"27532570","full_name":"inklabs/kommerce-core","owner":"inklabs","description":"PHP shopping cart core platform","archived":false,"fork":false,"pushed_at":"2018-09-06T03:46:43.000Z","size":3629,"stargazers_count":65,"open_issues_count":24,"forks_count":14,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-02-17T10:49:50.445Z","etag":null,"topics":["clean-code","ddd","ecommerce","oop","php","shopping-cart","solid","tdd"],"latest_commit_sha":null,"homepage":"https://kommerce-laravel-demo.jamieisaacs.com/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inklabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-12-04T09:13:08.000Z","updated_at":"2024-08-22T10:46:49.000Z","dependencies_parsed_at":"2022-08-21T12:20:29.444Z","dependency_job_id":null,"html_url":"https://github.com/inklabs/kommerce-core","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/inklabs/kommerce-core","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inklabs%2Fkommerce-core","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inklabs%2Fkommerce-core/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inklabs%2Fkommerce-core/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inklabs%2Fkommerce-core/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inklabs","download_url":"https://codeload.github.com/inklabs/kommerce-core/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inklabs%2Fkommerce-core/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27695289,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-12T02:00:06.775Z","response_time":129,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clean-code","ddd","ecommerce","oop","php","shopping-cart","solid","tdd"],"created_at":"2025-12-12T23:39:26.638Z","updated_at":"2025-12-12T23:39:28.992Z","avatar_url":"https://github.com/inklabs.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"Zen Kommerce Core\n=================\n[![Test Coverage](http://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://codeclimate.com/github/inklabs/kommerce-core)\n[![Build Status](https://travis-ci.org/inklabs/kommerce-core.svg?branch=master)](https://travis-ci.org/inklabs/kommerce-core)\n[![Downloads](https://img.shields.io/packagist/dt/inklabs/kommerce-core.svg)](https://packagist.org/packages/inklabs/kommerce-core)\n[![Apache License 2.0](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg)](https://github.com/inklabs/kommerce-core/blob/master/LICENSE.txt)\n\n## Introduction\n\nZen Kommerce is a PHP shopping cart system written with SOLID design principles.\nIt is PSR compatible, dependency free, and contains 100% code coverage using TDD practices.\n\nAll code (including tests) conform to the PSR-2 coding standards. The namespace and autoloader\nare using the PSR-4 standard.\n\n## Description\n\nThis project is over 62,000 lines of code. Unit tests account for 30-40% of that total and execute in\nunder 10 seconds. The repository tests use an in-memory SQLite database.\n\n## Design Patterns\n\n* [Design Patterns used in this project](docs/DesignPatterns).\n\n## Architecture\n\n![Flow of Control](https://i.imgur.com/ZkPtmsA.png)\n\n* \u003ca name=\"action\"\u003e\u003c/a\u003eAction\n    - Command and Queries are the Use Cases and main entry-point into the application.\n\n    - **Command Actions** are dispatched to the\n      [CommandBusInterface](src/Lib/Command/CommandBusInterface.php) to be handled.\n\n      ```php\n      $applyToShipping = true;\n      $command = new CreateStateTaxRateCommand('CA', 9.5, $applyToShipping);\n      $this-\u003edispatch($command);\n      ```\n\n        - There is no return value from the `$this-\u003edispatch(...)` method. Only exceptions are thrown if the\n          Command is invalid.\n\n    - **Query Actions** are dispatched to the\n      [QueryBusInterface](src/Lib/Query/QueryBusInterface.php) to be handled.\n      Instead of returning the [Product](src/Entity/Product.php) entity, the handler will inject a\n      [ProductDTOBuilder](src/EntityDTO/Builder/ProductDTOBuilder.php) object into the Response. The DTO Builder\n      is used to produce a [ProductDTO](src/EntityDTO/ProductDTO.php) which can be retrieved using the\n      `$response-\u003egetProductDTO()` method.\n\n      ```php\n      $productId = '15dc6044-910c-431a-a578-430759ef5dcf';\n      $query = new GetProductQuery($productId);\n      \n      /** @var GetProductResponse $response */\n      $response = $this-\u003edispatchQuery($query);\n\n      $productDTO = $response-\u003egetProductDTO();\n      var_export($productDTO);\n      ```\n\n      ```php\n        inklabs\\kommerce\\EntityDTO\\ProductDTO::__set_state([\n            'slug' =\u003e 'test-product',\n            'sku' =\u003e '5b6541751fd10',\n            'name' =\u003e 'Test Product',\n            'unitPrice' =\u003e 1200,\n            'quantity' =\u003e 10,\n            'isInventoryRequired' =\u003e false,\n            'isPriceVisible' =\u003e true,\n            'isActive' =\u003e true,\n            'isVisible' =\u003e true,\n            'isTaxable' =\u003e true,\n            'isShippable' =\u003e true,\n            'isInStock' =\u003e true,\n            'areAttachmentsEnabled' =\u003e false,\n            'shippingWeight' =\u003e 16,\n            'description' =\u003e null,\n            'rating' =\u003e null,\n            'tags' =\u003e [],\n            'images' =\u003e [],\n            'tagImages' =\u003e [],\n            'options' =\u003e [],\n            'textOptions' =\u003e [],\n            'productQuantityDiscounts' =\u003e [],\n            'optionProducts' =\u003e [],\n            'productAttributes' =\u003e [],\n            'price' =\u003e inklabs\\kommerce\\EntityDTO\\PriceDTO::__set_state([\n                'origUnitPrice' =\u003e 1200,\n                'unitPrice' =\u003e 1200,\n                'origQuantityPrice' =\u003e 1200,\n                'quantityPrice' =\u003e 1200,\n                'catalogPromotions' =\u003e [],\n                'productQuantityDiscounts' =\u003e []\n            ]),\n            'id' =\u003e inklabs\\kommerce\\Lib\\UUID::fromString('15dc6044-910c-431a-a578-430759ef5dcf'),\n            'created' =\u003e DateTime::__set_state([\n                'date' =\u003e '2018-08-04 06:04:26.000000',\n                'timezone_type' =\u003e 3,\n                'timezone' =\u003e 'UTC',\n            ]),\n            'updated' =\u003e null,\n            'createdFormatted' =\u003e 'August 3, 2018 11:04 pm PDT',\n            'updatedFormatted' =\u003e null,\n        ]);\n      ```\n\n\t   HTML Template:\n\n      ```html\n      Product: \u003c?=$productDTO-\u003ename?\u003e - \u003c?=$productDTO-\u003esku?\u003e\n      Price: \u003c?=$productDTO-\u003eprice-\u003eunitPrice?\u003e\n      Tag: \u003c?=$productDTO-\u003etags[0]-\u003ename?\u003e\n      ```\n\n    - Both implementations ([CommandBus](src/Lib/Command/CommandBus.php) and [QueryBus](src/Lib/Query/QueryBus.php))\n      defer to the [MapperInterface](src/Lib/MapperInterface.php) to determine the location of the\n      class to handle the execution.\n\n    - This CQRS strategy allows us to separate Commands from Queries while also keeping the [Entity](src/Entity)\n      business objects separate from the main application. We prefer not to expose internal classes containing\n      methods with business logic. This also serves to decouple the main application from the Use Cases handler\n      implementation. The main application only needs to know about the Use Case [Actions](src/Action).\n\n* \u003ca name=\"domain-event\"\u003e\u003c/a\u003eDomain Event\n    - Domain Events can be raised in the Entity layer and are dispatched in the service layer.\n\n    ```php\n    // UserEntity:\n    public function setPassword($password)\n    {\n        $this-\u003epasswordHash = // hash the password...\n\n        $this-\u003eraise(\n            new PasswordChangedEvent(\n                $this-\u003eid,\n                $this-\u003eemail,\n                $this-\u003egetFullName()\n            )\n        );\n    }\n    ```\n\n    ```php\n    // UserService:\n    $user = $this-\u003euserRepository-\u003efindOneById($userId);\n    $user-\u003esetPassword($password);\n    $this-\u003euserRepository-\u003eupdate($user);\n\n    $this-\u003eeventDispatcher-\u003edispatch($user-\u003ereleaseEvents());\n    ```\n\n    - Events can be dispatched directly in the service layer. (deprecated)\n    \n    ```php\n    // CartService:\n    $order = Order::fromCart($cart);\n    $this-\u003eorderRepository-\u003ecreate($order);\n\n    $this-\u003eeventDispatcher-\u003edispatchEvent(\n        new OrderCreatedFromCartEvent($order-\u003egetId())\n    );\n    ```\n\n* \u003ca name=\"service\"\u003e\u003c/a\u003eService\n    - These are the domain services to manage persisting domain state to the database through repositories. They contain\n      behavior related to multiple Entities and any business logic that does not fit any specific Entity or single Use Case.\n\n* \u003ca name=\"entity\"\u003e\u003c/a\u003eEntity\n    - These are plain old PHP objects. You will not find any ORM code or external dependencies here. This is where\n      the relationships between objects are constructed. An Entity contains business logic and behavior with high cohesion to\n      its own properties. Business logic related to the data of a single instance of an Entity belongs here.\n\n      ```php\n      $tag = new Entity\\Tag\n      $tag-\u003esetName('Test Tag');\n\n      $product = new Entity\\Product;\n      $product-\u003esetName('Test Product');\n      $product-\u003esetUnitPrice(500);\n      $product-\u003esetQuantity(1);\n      $product-\u003esetIsInventoryRequired(true);\n      $product-\u003eaddTag($tag);\n      \n      if ($product-\u003einStock()) {\n        // Show add to cart button\n      }\n      ```\n\n* \u003ca name=\"repository\"\u003e\u003c/a\u003eEntityRepository\n    - This module is responsible for storing and retrieving entities. Doctrine 2 is used in this layer to hydrate Entities\n      using the Data Mapper Pattern.\n\n      ```php\n      $productRepository = $this-\u003eentityManager-\u003egetRepository(Product::class);\n\n      $productId = 1;\n      $product = $productRepository-\u003efindOneById($productId);\n      $product-\u003esetUnitPrice(600);\n\n      $productRepository-\u003epersist($product);\n      ```\n      \n* \u003ca name=\"entitydto\"\u003e\u003c/a\u003eEntityDTO\n    - These classes are simple anemic objects with no business logic. Data is accessible via public class member variables. \n      Using the EntityDTOBuilder, the complete network graph relationships are available (e.g., withAllData()) prior to\n      calling build(). The primary reason for using these Data Transfer Objects (DTO) is to flatten the object graph from\n      lazy loaded Doctrine proxy objects on the Entities for use in view templates. This avoids lazy loaded queries from\n      being executed outside the core application and somewhere they don't belong, such as in a view template.\n\n      ```php\n      $product = new Entity\\Product;\n      $product-\u003eaddTag(new Entity\\Tag);\n\n      $productDTO = $product-\u003egetDTOBuilder()\n        -\u003ewithAllData(new Lib\\Pricing)\n        -\u003ebuild();\n\n      echo $productDTO-\u003esku;\n      echo $productDTO-\u003eprice-\u003eunitPrice;\n      echo $productDTO-\u003etags[0]-\u003ename;\n      ```\n\n* \u003ca name=\"lib\"\u003e\u003c/a\u003eLib\n    - This is where you will find a variety of utility code including the Payment Gateway (src/Lib/PaymentGateway).\n\n      ```php\n      $creditCard = new Entity\\CreditCard;\n      $creditCard-\u003esetName('John Doe');\n      $creditCard-\u003esetZip5('90210');\n      $creditCard-\u003esetNumber('4242424242424242');\n      $creditCard-\u003esetCvc('123');\n      $creditCard-\u003esetExpirationMonth('1');\n      $creditCard-\u003esetExpirationYear('2020');\n\n      $chargeRequest = new Lib\\PaymentGateway\\ChargeRequest;\n      $chargeRequest-\u003esetCreditCard($creditCard);\n      $chargeRequest-\u003esetAmount(2000);\n      $chargeRequest-\u003esetCurrency('usd');\n      $chargeRequest-\u003esetDescription('test@example.com');\n\n      $stripe = new Lib\\PaymentGateway\\StripeFake;\n      $charge = $stripe-\u003egetCharge($chargeRequest);\n      ```\n\n## Installation\n\nAdd the following lines to your ``composer.json`` file.\n\n```JSON\n{\n    \"require\": {\n        \"inklabs/kommerce-core\": \"dev-master\"\n    }\n}\n```\n\n```\n   composer install\n```\n\n## Unit Tests:\n\n\u003cpre\u003e\n    vendor/bin/phpunit\n\u003c/pre\u003e\n\n### With Code Coverage:\n\n\u003cpre\u003e\n    vendor/bin/phpunit --coverage-text --coverage-html coverage_report\n\u003c/pre\u003e\n\n## Run Coding Standards Test:\n\n\u003cpre\u003e\n    vendor/bin/phpcs -p --standard=PSR2 src/ tests/\n\u003c/pre\u003e\n\n## Count Lines of Code:\n\n\u003cpre\u003e\n    vendor/bin/phploc src/ tests/ --names=\"*.php,*.xml\"\n\u003c/pre\u003e\n\n## Export SQL\n\n\u003cpre\u003e\n    vendor/bin/doctrine orm:schema-tool:create --dump-sql\n    vendor/bin/doctrine orm:schema-tool:update --dump-sql\n\u003c/pre\u003e\n\n## License\n\n```\nCopyright 2014 Jamie Isaacs pdt256@gmail.com\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finklabs%2Fkommerce-core","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finklabs%2Fkommerce-core","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finklabs%2Fkommerce-core/lists"}