{"id":13623499,"url":"https://github.com/spotorm/spot2","last_synced_at":"2026-01-15T04:00:01.302Z","repository":{"id":37444774,"uuid":"20979572","full_name":"spotorm/spot2","owner":"spotorm","description":"Spot v2.x DataMapper built on top of Doctrine's Database Abstraction Layer","archived":false,"fork":false,"pushed_at":"2025-08-08T13:34:14.000Z","size":429,"stargazers_count":600,"open_issues_count":67,"forks_count":98,"subscribers_count":27,"default_branch":"master","last_synced_at":"2026-01-04T02:00:25.026Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://phpdatamapper.com","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spotorm.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-06-18T22:02:22.000Z","updated_at":"2025-08-08T13:34:18.000Z","dependencies_parsed_at":"2024-05-05T07:47:25.201Z","dependency_job_id":null,"html_url":"https://github.com/spotorm/spot2","commit_stats":{"total_commits":216,"total_committers":45,"mean_commits":4.8,"dds":0.6527777777777778,"last_synced_commit":"4867f7c1d91baf5772288d96b89d8a67bd4fa5be"},"previous_names":["vlucas/spot2"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/spotorm/spot2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotorm%2Fspot2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotorm%2Fspot2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotorm%2Fspot2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotorm%2Fspot2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spotorm","download_url":"https://codeload.github.com/spotorm/spot2/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spotorm%2Fspot2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28217521,"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":"2026-01-05T02:00:06.358Z","response_time":57,"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":[],"created_at":"2024-08-01T21:01:32.486Z","updated_at":"2026-01-15T04:00:01.285Z","avatar_url":"https://github.com/spotorm.png","language":"PHP","readme":"Spot DataMapper ORM v2.0 [![Build Status](https://travis-ci.org/spotorm/spot2.svg)](https://travis-ci.org/spotorm/spot2)\n========================\nSpot v2.x is built on the [Doctrine\nDBAL](http://www.doctrine-project.org/projects/dbal.html), and targets PHP\n5.4+.\n\nThe aim of Spot is to be a lightweight DataMapper alternative that is clear,\nefficient, and simple - and doesn't use annotations or proxy classes.\n\nUsing Spot In Your Project\n--------------------------\n\nSpot is a standalone ORM that can be used in any project. Follow the\ninstructions below to get Spot setup in your project.\n\nInstallation with Composer\n--------------------------\n\n```bash\ncomposer require vlucas/spot2\n```\n\n\nConnecting to a Database\n------------------------\n\nThe `Spot\\Locator` object is the main point of access to spot that you will\nhave to be able to access from everywhere you need to run queries or work with\nyour entities. It is responsible for loading mappers and managing configuration.\nTo create a Locator, you will need a `Spot\\Config` object.\n\nThe `Spot\\Config` object stores and references database connections by name.\nCreate a new instance of `Spot\\Config` and add database connections with\nDSN strings so Spot can establish a database connection, then create your\nlocator object:\n\n```php\n$cfg = new \\Spot\\Config();\n\n// MySQL\n$cfg-\u003eaddConnection('mysql', 'mysql://user:password@localhost/database_name');\n// Sqlite\n$cfg-\u003eaddConnection('sqlite', 'sqlite://path/to/database.sqlite');\n\n$spot = new \\Spot\\Locator($cfg);\n```\n\nYou can also use [DBAL-compatible configuration\narrays](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html)\ninstead of DSN strings if you prefer:\n\n```php\n$cfg-\u003eaddConnection('mysql', [\n    'dbname' =\u003e 'mydb',\n    'user' =\u003e 'user',\n    'password' =\u003e 'secret',\n    'host' =\u003e 'localhost',\n    'driver' =\u003e 'pdo_mysql',\n]);\n```\n\nAccessing the Locator\n--------------------\n\nSince you have to have access to your mapper anywhere you use the\ndatabase, most people create a helper method to create a mapper instance\nonce and then return the same instance when required again. Such a\nhelper method might look something like this:\n\n```php\nfunction spot() {\n    static $spot;\n    if($spot === null) {\n        $spot = new \\Spot\\Locator();\n        $spot-\u003econfig()-\u003eaddConnection('test_mysql', 'mysql://user:password@localhost/database_name');\n    }\n    return $spot;\n}\n```\n\nIf you are using a framework with a dependency injection container or service,\nyou will want to use it so that the `Spot\\Locator` object is available\neverywhere in your application that you need it.\n\nGetting A Mapper\n----------------\n\nSince Spot follows the DataMapper design pattern, you will need a mapper\ninstance for working with object Entities and database tables. You can get a\nmapper instance from the `Spot\\Locator` object's `mapper` method by providing\nthe fully qualified entity namespace + class name:\n\n```php\n$postMapper = $spot-\u003emapper('Entity\\Post');\n```\n\nMappers only work with one entity type, so you will need one mapper per entity\nclass you work with (i.e. to save an Entity\\Post, you will need the appropriate\nmapper, and to save an Entity\\Comment, you will need a comment mapper, not the\nsame post mapper. Relations will automatically be loaded and handled by their\ncorresponding mapper by Spot.\n\n**NOTE: You do NOT have to create a mapper for each entity unless you need\ncustom finder methods or other custom logic. If there is no entity-specific\nmapper for the entity you want, Spot will load the generic mapper for you and\nreturn it.**\n\nCreating Entities\n-----------------\n\nEntity classes can be named and namespaced however you want to set them\nup within your project structure. For the following examples, the\nEntities will just be prefixed with an `Entity` namespace for easy psr-0\ncompliant autoloading.\n\n```php\nnamespace Entity;\n\nuse Spot\\EntityInterface as Entity;\nuse Spot\\MapperInterface as Mapper;\n\nclass Post extends \\Spot\\Entity\n{\n    protected static $table = 'posts';\n\n    public static function fields()\n    {\n        return [\n            'id'           =\u003e ['type' =\u003e 'integer', 'autoincrement' =\u003e true, 'primary' =\u003e true],\n            'title'        =\u003e ['type' =\u003e 'string', 'required' =\u003e true],\n            'body'         =\u003e ['type' =\u003e 'text', 'required' =\u003e true],\n            'status'       =\u003e ['type' =\u003e 'integer', 'default' =\u003e 0, 'index' =\u003e true],\n            'author_id'    =\u003e ['type' =\u003e 'integer', 'required' =\u003e true],\n            'date_created' =\u003e ['type' =\u003e 'datetime', 'value' =\u003e new \\DateTime()]\n        ];\n    }\n\n    public static function relations(Mapper $mapper, Entity $entity)\n    {\n        return [\n            'tags' =\u003e $mapper-\u003ehasManyThrough($entity, 'Entity\\Tag', 'Entity\\PostTag', 'tag_id', 'post_id'),\n            'comments' =\u003e $mapper-\u003ehasMany($entity, 'Entity\\Post\\Comment', 'post_id')-\u003eorder(['date_created' =\u003e 'ASC']),\n            'author' =\u003e $mapper-\u003ebelongsTo($entity, 'Entity\\Author', 'author_id')\n        ];\n    }\n}\n```\n\nUsing Custom Mappers\n--------------------\n\nAlthough you do not have to create a mapper for each entity, sometimes it is\nnice to create one if you have a lot of custom finder methods, or want a better\nplace to contain the logic of building all the queries you need.\n\nJust specify the full mapper class name in your entity:\n```php\nnamespace Entity;\n\nclass Post extends \\Spot\\Entity\n{\n    protected static $mapper = 'Entity\\Mapper\\Post';\n\n    // ... snip ...\n}\n```\n\nAnd then create your mapper:\n```php\nnamespace Entity\\Mapper;\n\nuse Spot\\Mapper;\n\nclass Post extends Mapper\n{\n    /**\n     * Get 10 most recent posts for display on the sidebar\n     *\n     * @return \\Spot\\Query\n     */\n    public function mostRecentPostsForSidebar()\n    {\n        return $this-\u003ewhere(['status' =\u003e 'active'])\n            -\u003eorder(['date_created' =\u003e 'DESC'])\n            -\u003elimit(10);\n    }\n}\n```\n\nThen when you load the mapper like normal, Spot will see the custom\n`Entity\\Post::$mapper` you defined, and load that instead of the generic one,\nallowing you to call your custom method:\n\n```php\n$mapper = $spot-\u003emapper('Entity\\Post');\n$sidebarPosts = $mapper-\u003emostRecentPostsForSidebar();\n```\n\nField Types\n-----------\n\nSince Spot v2.x is built on top of DBAL, all the [DBAL\ntypes](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html)\nare used and fully supported in Spot:\n\nInteger Types\n * `smallint`\n * `integer`\n * `bigint`\n\nDecimal Types\n * `decimal`\n * `float`\n\nString Types\n * `string`\n * `text`\n * `guid`\n\nBinary String Types\n * `binary`\n * `blob`\n\nBoolean/Bit Types\n * `boolean`\n\nDate and Time Types\n * `date`\n * `datetime`\n * `datetimetz`\n * `time`\n\nArray Types\n * `array` - PHP serialize/deserialze\n * `simple_array` - PHP implode/explode\n * `json_array` - json_encode/json_decode\n\nObject Types\n * `object` - PHP serialize/deserialze\n\nPlease read the [Doctrine DBAL Types Reference\nPage](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html)\nthoroughly for more information and types and cross-database support. Some\ntypes may be stored differently on different databases, depending on database\nvendor support and other factors.\n\n#### Registering Custom Field Types\n\nIf you want to register your own custom field type with custom\nfunctionality on get/set, have a look at the [Custom Mapping Types on the DBAL\nreference page](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types).\n\nSince Spot uses the DBAL internally, there are no additional changes you have\nto make for your custom type to work with Spot.\n\nMigrations / Creating and Updating Tables\n-----------------------------------------\n\nSpot comes with a method for running migrations on Entities that will\nautomatically CREATE and ALTER tables based on the current Entity's `fields`\ndefinition.\n\n```php\n$mapper = $spot-\u003emapper('Entity\\Post');\n$mapper-\u003emigrate();\n```\n\nYour database should now have the `posts` table in it, with all the fields you\ndescribed in your `Post` entity.\n\n**NOTE: Please note that re-naming columns is not supported in migrations because\nthere is no way for spot to know which column you renamed to what - Spot will\nsee a new column that needs to be created, and a column that no longer exists\nand needs to be dropped. This could result in data loss during an\nauto-migration.**\n\nFinders (Mapper)\n----------------\n\nThe main finders used most are `all` to return a collection of entities,\nand `first` or `get` to return a single entity matching the conditions.\n\n### all()\n\nFind all entities and return a `Spot\\Entity\\Collection` of loaded `Spot\\Entity`\nobjects.\n\n### where([conditions])\n\nFind all entities that match the given conditions and return a\n`Spot\\Entity\\Collection` of loaded `Spot\\Entity` objects.\n\n```php\n// Where can be called directly from the mapper\n$posts = $mapper-\u003ewhere(['status' =\u003e 1]);\n\n// Or chained using the returned `Spot\\Query` object - results identical to above\n$posts = $mapper-\u003eall()-\u003ewhere(['status' =\u003e 1]);\n\n// Or more explicitly using using `select`, which always returns a `Spot\\Query` object\n$posts = $mapper-\u003eselect()-\u003ewhere(['status' =\u003e 1]);\n```\n\nSince a `Spot\\Query` object is returned, conditions and other statements\ncan be chained in any way or order you want. The query will be\nlazy-executed on interation or `count`, or manually by ending the chain with a\ncall to `execute()`.\n\n### first([conditions])\n\nFind and return a single `Spot\\Entity` object that matches the criteria.\n\n```php\n$post = $mapper-\u003efirst(['title' =\u003e \"Test Post\"]);\n```\n\nOr `first` can be used on a previous query with `all` to fetch only the first\nmatching record.\n\n```php\n$post = $mapper-\u003eall(['title' =\u003e \"Test Post\"])-\u003efirst();\n```\n\nA call to `first` will always execute the query immediately, and return either\na single loaded entity object, or boolean `false`.\n\n### Conditional Queries\n\n```php\n# All posts with a 'published' status, descending by date_created\n$posts = $mapper-\u003eall()\n    -\u003ewhere(['status' =\u003e 'published'])\n    -\u003eorder(['date_created' =\u003e 'DESC']);\n\n# All posts that are not published\n$posts = $mapper-\u003eall()\n    -\u003ewhere(['status \u003c\u003e' =\u003e 'published'])\n\n# All posts created before 3 days ago\n$posts = $mapper-\u003eall()\n    -\u003ewhere(['date_created \u003c' =\u003e new \\DateTime('-3 days')]);\n\n# Posts with 'id' of 1, 2, 5, 12, or 15 - Array value = automatic \"IN\" clause\n$posts = $mapper-\u003eall()\n    -\u003ewhere(['id' =\u003e [1, 2, 5, 12, 15]]);\n```\n\n### Joins\n\nJoins are currently not enabled by Spot's query builder. The Doctine DBAL query\nbuilder does provide full support for them, so they may be enabled in the\nfuture.\n\n### Custom Queries\n\nWhile ORMs like Spot are very nice to use, if you need to do complex queries,\nit's best to just use custom queries with the SQL you know and love.\n\nSpot provides a `query` method that allows you to run custom SQL, and load the\nresults into a normal collection of entity objects. This way, you can easily run\ncustom SQL queries with all the same ease of use and convenience as the\nbuilt-in finder methods and you won't have to do any special handling.\n\n#### Using Custom SQL\n\n```php\n$posts = $mapper-\u003equery(\"SELECT * FROM posts WHERE id = 1\");\n```\n\n#### Using Query Parameters\n\n```php\n$posts = $mapper-\u003equery(\"SELECT * FROM posts WHERE id = ?\", [1]);\n```\n\n#### Using Named Placeholders\n\n```php\n$posts = $mapper-\u003equery(\"SELECT * FROM posts WHERE id = :id\", ['id' =\u003e 1]);\n```\n\n**NOTE: Spot will load ALL returned columns on the target entity from the query\nyou run. So if you perform a JOIN or get more data than the target entity\nnormally has, it will just be loaded on the target entity, and no attempt will\nbe made to map the data to other entities or to filter it based on only the\ndefined fields.**\n\nRelations\n---------\n\nRelations are convenient ways to access related, parent, and child entities from\nanother loaded entity object. An example might be `$post-\u003ecomments` to query for\nall the comments related to the current `$post` object.\n\n### Live Query Objects\n\nAll relations are returned as instances of relation classes that extend\n`Spot\\Relation\\RelationAbstract`. This class holds a `Spot\\Query` object\ninternally, and allows you to chain your own query modifications on it so you\ncan do custom things with relations, like ordering, adding more query\nconditions, etc.\n\n```php\n$mapper-\u003ehasMany($entity, 'Entity\\Comment', 'post_id')\n    -\u003ewhere(['status' =\u003e 'active'])\n    -\u003eorder(['date_created' =\u003e 'ASC']);\n```\n\nAll of these query modifications are held in a queue, and are run when the\nrelation is actually executed (on `count` or `foreach` iteration, or when\n`execute` is explicitly called).\n\n### Eager Loading\n\nAll relation types are lazy-loaded by default, and can be eager-loaded to\nsolve the N+1 query problem using the `with` method:\n\n```php\n$posts = $posts-\u003eall()-\u003ewith('comments');\n```\n\nMultiple relations can be eager-loaded using an array:\n```php\n$posts = $posts-\u003eall()-\u003ewith(['comments', 'tags']);\n```\n\n### Relation Types\n\nEntity relation types are:\n\n * `HasOne`\n * `BelongsTo`\n * `HasMany`\n * `HasManyThrough`\n\n### HasOne\n\nHasOne is a relation where the *related object has a field which points to the\ncurrent object* - an example might be `User` has one `Profile`.\n\n#### Method\n\n```php\n$mapper-\u003ehasOne(Entity $entity, $foreignEntity, $foreignKey)\n```\n * `$entity` - The current entity instance\n * `$foreignEntity` - Name of the entity you want to load\n * `$foreignKey` - Field name on the `$foreignEntity` that matches up with the\n   primary key of the current entity\n\n#### Example\n\n```php\nnamespace Entity;\n\nuse Spot\\EntityInterface as Entity;\nuse Spot\\MapperInterface as Mapper;\n\nclass User extends \\Spot\\Entity\n{\n    protected static $table = 'users';\n\n    public static function fields()\n    {\n        return [\n            'id'           =\u003e ['type' =\u003e 'integer', 'autoincrement' =\u003e true, 'primary' =\u003e true],\n            'username'     =\u003e ['type' =\u003e 'string', 'required' =\u003e true],\n            'email'        =\u003e ['type' =\u003e 'string', 'required' =\u003e true],\n            'status'       =\u003e ['type' =\u003e 'integer', 'default' =\u003e 0, 'index' =\u003e true],\n            'date_created' =\u003e ['type' =\u003e 'datetime', 'value' =\u003e new \\DateTime()]\n        ];\n    }\n\n    public static function relations(Mapper $mapper, Entity $entity)\n    {\n        return [\n            'profile' =\u003e $mapper-\u003ehasOne($entity, 'Entity\\User\\Profile', 'user_id')\n        ];\n    }\n}\n```\n\nIn this scenario, the `Entity\\User\\Profile` entity has a field named `user_id`\nwhich the `Entity\\User`'s `id` field as a value. Note that *no field exists on\nthis entity for this relation, but rather the related entity*.\n\n### BelongsTo\n\nBelongsTo is a relation where the *current object has a field which points to\nthe related object* - an example might be `Post` belongs to `User`.\n\n#### Method\n\n```php\n$mapper-\u003ebelongsTo(Entity $entity, $foreignEntity, $localKey)\n```\n * `$entity` - The current entity instance\n * `$foreignEntity` - Name of the entity you want to load\n * `$localKey` - Field name on the current entity that matches up with the\n   primary key of `$foreignEntity` (the one you want to load)\n\n#### Example\n\n```php\nnamespace Entity;\n\nuse Spot\\EntityInterface as Entity;\nuse Spot\\MapperInterface as Mapper;\n\nclass Post extends \\Spot\\Entity\n{\n    protected static $table = 'posts';\n\n    public static function fields()\n    {\n        return [\n            'id'           =\u003e ['type' =\u003e 'integer', 'autoincrement' =\u003e true, 'primary' =\u003e true],\n            'user_id'      =\u003e ['type' =\u003e 'integer', 'required' =\u003e true],\n            'title'        =\u003e ['type' =\u003e 'string', 'required' =\u003e true],\n            'body'         =\u003e ['type' =\u003e 'text', 'required' =\u003e true],\n            'status'       =\u003e ['type' =\u003e 'integer', 'default' =\u003e 0, 'index' =\u003e true],\n            'date_created' =\u003e ['type' =\u003e 'datetime', 'value' =\u003e new \\DateTime()]\n        ];\n    }\n\n    public static function relations(Mapper $mapper, Entity $entity)\n    {\n        return [\n            'user' =\u003e $mapper-\u003ebelongsTo($entity, 'Entity\\User', 'user_id')\n        ];\n    }\n}\n```\n\nIn this scenario, the `Entity\\Post` entity has a field named `user_id` which is\nthe `Entity\\User`'s `id` field's value. Note that *the field exists on this\nentity for this relation, but not on the related entity*.\n\n### HasMany\n\nHasMany is used where a single record relates to multiple other records - an\nexample might be `Post` has many `Comments`.\n\n#### Method\n\n```php\n$mapper-\u003ehasMany(Entity $entity, $entityName, $foreignKey, $localValue = null)\n```\n * `$entity` - The current entity instance\n * `$entityName` - Name of the entity you want to load a collection of\n * `$foreignKey` - Field name on the `$entityName` that matches up with the\n   current entity's primary key\n\n#### Example\n\nWe start by adding a `comments` relation to our `Post` object:\n```php\nnamespace Entity;\n\nuse Spot\\EntityInterface as Entity;\nuse Spot\\MapperInterface as Mapper;\n\nclass Post extends Spot\\Entity\n{\n    protected static $table = 'posts';\n\n    public static function fields()\n    {\n        return [\n            'id'           =\u003e ['type' =\u003e 'integer', 'autoincrement' =\u003e true, 'primary' =\u003e true],\n            'title'        =\u003e ['type' =\u003e 'string', 'required' =\u003e true],\n            'body'         =\u003e ['type' =\u003e 'text', 'required' =\u003e true],\n            'status'       =\u003e ['type' =\u003e 'integer', 'default' =\u003e 0, 'index' =\u003e true],\n            'date_created' =\u003e ['type' =\u003e 'datetime', 'value' =\u003e new \\DateTime()]\n        ];\n    }\n\n    public static function relations(Mapper $mapper, Entity $entity)\n    {\n        return [\n            'comments' =\u003e $mapper-\u003ehasMany($entity, 'Entity\\Comment', 'post_id')-\u003eorder(['date_created' =\u003e 'ASC']),\n        ];\n    }\n}\n```\n\nAnd add a `Entity\\Post\\Comment` object with a 'belongsTo' relation back to the post:\n\n```php\nnamespace Entity;\n\nclass Comment extends \\Spot\\Entity\n{\n    // ... snip ...\n\n    public static function relations(Mapper $mapper, Entity $entity)\n    {\n        return [\n            'post' =\u003e $mapper-\u003ebelongsTo($entity, 'Entity\\Post', 'post_id')\n        ];\n    }\n}\n```\n\n### HasManyThrough\n\nHasManyThrough is used for many-to-many relationships. An good example is\ntagging. A post has many tags, and a tag has many posts. This relation is\na bit more complex than the others, because a HasManyThrough requires a\njoin table and mapper.\n\n#### Method\n```php\n$mapper-\u003ehasManyThrough(Entity $entity, string $hasManyEntity, string $throughEntity, string $selectField, string $whereField)\n```\n * `$entity` - The current entity instance\n * `$hasManyEntity` - This is the target entity you want a collection of. In this case, we want a collection of `Entity\\Tag` objects.\n * `$throughEntity` - Name of the entity we are going through to get what we want - In this case, `Entity\\PostTag`.\n * `$selectField` - Name of the field on the `$throughEntity` that will select records by the primary key of `$hasManyEntity`.\n * `$whereField` - Name of the field on the `$throughEntity` to select records by the current entities' primary key (we have a post, so this will be the `Entity\\PostTag-\u003epost_id` field).\n\n#### Example\n\nWe need to add the `tags` relation to our `Post` entity, specifying query\nconditions for both sides of the relation.\n\n```php\nnamespace Entity;\n\nuse Spot\\EntityInterface as Entity;\nuse Spot\\MapperInterface as Mapper;\n\nclass Post extends Spot\\Entity\n{\n    protected static $table = 'posts';\n\n    public static function fields()\n    {\n        return [\n            'id'           =\u003e ['type' =\u003e 'integer', 'autoincrement' =\u003e true, 'primary' =\u003e true],\n            'title'        =\u003e ['type' =\u003e 'string', 'required' =\u003e true],\n            'body'         =\u003e ['type' =\u003e 'text', 'required' =\u003e true],\n            'status'       =\u003e ['type' =\u003e 'integer', 'default' =\u003e 0, 'index' =\u003e true],\n            'date_created' =\u003e ['type' =\u003e 'datetime', 'value' =\u003e new \\DateTime()]\n        ];\n    }\n\n    public static function relations(Mapper $mapper, Entity $entity)\n    {\n        return [\n            'tags' =\u003e $mapper-\u003ehasManyThrough($entity, 'Entity\\Tag', 'Entity\\PostTag', 'tag_id', 'post_id'),\n        ];\n    }\n```\n\n#### Explanation\n\nThe result we want is a collection of `Entity\\Tag` objects where the id equals\nthe `post_tags.tag_id` column. We get this by going through the\n`Entity\\PostTags` entity, using the current loaded post id matching\n`post_tags.post_id`.\n\n","funding_links":[],"categories":["Table of Contents","PHP","目录"],"sub_categories":["Database","数据库 Database"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspotorm%2Fspot2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspotorm%2Fspot2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspotorm%2Fspot2/lists"}