Spot v2.x DataMapper built on top of Doctrine's Database Abstraction Layer

Related tags

Miscellaneous spot2
Overview

Spot DataMapper ORM v2.0 Build Status

Spot v2.x is built on the Doctrine DBAL, and targets PHP 5.4+.

The aim of Spot is to be a lightweight DataMapper alternative that is clear, efficient, and simple - and doesn't use annotations or proxy classes.

Using Spot In Your Project

Spot is a standalone ORM that can be used in any project. Follow the instructions below to get Spot setup in your project.

Installation with Composer

composer require vlucas/spot2

Connecting to a Database

The Spot\Locator object is the main point of access to spot that you will have to be able to access from everywhere you need to run queries or work with your entities. It is responsible for loading mappers and managing configuration. To create a Locator, you will need a Spot\Config object.

The Spot\Config object stores and references database connections by name. Create a new instance of Spot\Config and add database connections with DSN strings so Spot can establish a database connection, then create your locator object:

$cfg = new \Spot\Config();

// MySQL
$cfg->addConnection('mysql', 'mysql://user:password@localhost/database_name');
// Sqlite
$cfg->addConnection('sqlite', 'sqlite://path/to/database.sqlite');

$spot = new \Spot\Locator($cfg);

You can also use DBAL-compatible configuration arrays instead of DSN strings if you prefer:

$cfg->addConnection('mysql', [
    'dbname' => 'mydb',
    'user' => 'user',
    'password' => 'secret',
    'host' => 'localhost',
    'driver' => 'pdo_mysql',
]);

Accessing the Locator

Since you have to have access to your mapper anywhere you use the database, most people create a helper method to create a mapper instance once and then return the same instance when required again. Such a helper method might look something like this:

function spot() {
    static $spot;
    if($spot === null) {
        $spot = new \Spot\Locator();
        $spot->config()->addConnection('test_mysql', 'mysql://user:password@localhost/database_name');
    }
    return $spot;
}

If you are using a framework with a dependency injection container or service, you will want to use it so that the Spot\Locator object is available everywhere in your application that you need it.

Getting A Mapper

Since Spot follows the DataMapper design pattern, you will need a mapper instance for working with object Entities and database tables. You can get a mapper instance from the Spot\Locator object's mapper method by providing the fully qualified entity namespace + class name:

$postMapper = $spot->mapper('Entity\Post');

Mappers only work with one entity type, so you will need one mapper per entity class you work with (i.e. to save an Entity\Post, you will need the appropriate mapper, and to save an Entity\Comment, you will need a comment mapper, not the same post mapper. Relations will automatically be loaded and handled by their corresponding mapper by Spot.

NOTE: You do NOT have to create a mapper for each entity unless you need custom finder methods or other custom logic. If there is no entity-specific mapper for the entity you want, Spot will load the generic mapper for you and return it.

Creating Entities

Entity classes can be named and namespaced however you want to set them up within your project structure. For the following examples, the Entities will just be prefixed with an Entity namespace for easy psr-0 compliant autoloading.

namespace Entity;

use Spot\EntityInterface as Entity;
use Spot\MapperInterface as Mapper;

class Post extends \Spot\Entity
{
    protected static $table = 'posts';

    public static function fields()
    {
        return [
            'id'           => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
            'title'        => ['type' => 'string', 'required' => true],
            'body'         => ['type' => 'text', 'required' => true],
            'status'       => ['type' => 'integer', 'default' => 0, 'index' => true],
            'author_id'    => ['type' => 'integer', 'required' => true],
            'date_created' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }

    public static function relations(Mapper $mapper, Entity $entity)
    {
        return [
            'tags' => $mapper->hasManyThrough($entity, 'Entity\Tag', 'Entity\PostTag', 'tag_id', 'post_id'),
            'comments' => $mapper->hasMany($entity, 'Entity\Post\Comment', 'post_id')->order(['date_created' => 'ASC']),
            'author' => $mapper->belongsTo($entity, 'Entity\Author', 'author_id')
        ];
    }
}

Using Custom Mappers

Although you do not have to create a mapper for each entity, sometimes it is nice to create one if you have a lot of custom finder methods, or want a better place to contain the logic of building all the queries you need.

Just specify the full mapper class name in your entity:

namespace Entity;

class Post extends \Spot\Entity
{
    protected static $mapper = 'Entity\Mapper\Post';

    // ... snip ...
}

And then create your mapper:

namespace Entity\Mapper;

use Spot\Mapper;

class Post extends Mapper
{
    /**
     * Get 10 most recent posts for display on the sidebar
     *
     * @return \Spot\Query
     */
    public function mostRecentPostsForSidebar()
    {
        return $this->where(['status' => 'active'])
            ->order(['date_created' => 'DESC'])
            ->limit(10);
    }
}

Then when you load the mapper like normal, Spot will see the custom Entity\Post::$mapper you defined, and load that instead of the generic one, allowing you to call your custom method:

$mapper = $spot->mapper('Entity\Post');
$sidebarPosts = $mapper->mostRecentPostsForSidebar();

Field Types

Since Spot v2.x is built on top of DBAL, all the DBAL types are used and fully supported in Spot:

Integer Types

  • smallint
  • integer
  • bigint

Decimal Types

  • decimal
  • float

String Types

  • string
  • text
  • guid

Binary String Types

  • binary
  • blob

Boolean/Bit Types

  • boolean

Date and Time Types

  • date
  • datetime
  • datetimetz
  • time

Array Types

  • array - PHP serialize/deserialze
  • simple_array - PHP implode/explode
  • json_array - json_encode/json_decode

Object Types

  • object - PHP serialize/deserialze

Please read the Doctrine DBAL Types Reference Page thoroughly for more information and types and cross-database support. Some types may be stored differently on different databases, depending on database vendor support and other factors.

Registering Custom Field Types

If you want to register your own custom field type with custom functionality on get/set, have a look at the Custom Mapping Types on the DBAL reference page.

Since Spot uses the DBAL internally, there are no additional changes you have to make for your custom type to work with Spot.

Migrations / Creating and Updating Tables

Spot comes with a method for running migrations on Entities that will automatically CREATE and ALTER tables based on the current Entity's fields definition.

$mapper = $spot->mapper('Entity\Post');
$mapper->migrate();

Your database should now have the posts table in it, with all the fields you described in your Post entity.

NOTE: Please note that re-naming columns is not supported in migrations because there is no way for spot to know which column you renamed to what - Spot will see a new column that needs to be created, and a column that no longer exists and needs to be dropped. This could result in data loss during an auto-migration.

Finders (Mapper)

The main finders used most are all to return a collection of entities, and first or get to return a single entity matching the conditions.

all()

Find all entities and return a Spot\Entity\Collection of loaded Spot\Entity objects.

where([conditions])

Find all entities that match the given conditions and return a Spot\Entity\Collection of loaded Spot\Entity objects.

// Where can be called directly from the mapper
$posts = $mapper->where(['status' => 1]);

// Or chained using the returned `Spot\Query` object - results identical to above
$posts = $mapper->all()->where(['status' => 1]);

// Or more explicitly using using `select`, which always returns a `Spot\Query` object
$posts = $mapper->select()->where(['status' => 1]);

Since a Spot\Query object is returned, conditions and other statements can be chained in any way or order you want. The query will be lazy-executed on interation or count, or manually by ending the chain with a call to execute().

first([conditions])

Find and return a single Spot\Entity object that matches the criteria.

$post = $mapper->first(['title' => "Test Post"]);

Or first can be used on a previous query with all to fetch only the first matching record.

$post = $mapper->all(['title' => "Test Post"])->first();

A call to first will always execute the query immediately, and return either a single loaded entity object, or boolean false.

Conditional Queries

# All posts with a 'published' status, descending by date_created
$posts = $mapper->all()
    ->where(['status' => 'published'])
    ->order(['date_created' => 'DESC']);

# All posts that are not published
$posts = $mapper->all()
    ->where(['status <>' => 'published'])

# All posts created before 3 days ago
$posts = $mapper->all()
    ->where(['date_created <' => new \DateTime('-3 days')]);

# Posts with 'id' of 1, 2, 5, 12, or 15 - Array value = automatic "IN" clause
$posts = $mapper->all()
    ->where(['id' => [1, 2, 5, 12, 15]]);

Joins

Joins are currently not enabled by Spot's query builder. The Doctine DBAL query builder does provide full support for them, so they may be enabled in the future.

Custom Queries

While ORMs like Spot are very nice to use, if you need to do complex queries, it's best to just use custom queries with the SQL you know and love.

Spot provides a query method that allows you to run custom SQL, and load the results into a normal collection of entity objects. This way, you can easily run custom SQL queries with all the same ease of use and convenience as the built-in finder methods and you won't have to do any special handling.

Using Custom SQL

$posts = $mapper->query("SELECT * FROM posts WHERE id = 1");

Using Query Parameters

$posts = $mapper->query("SELECT * FROM posts WHERE id = ?", [1]);

Using Named Placeholders

$posts = $mapper->query("SELECT * FROM posts WHERE id = :id", ['id' => 1]);

NOTE: Spot will load ALL returned columns on the target entity from the query you run. So if you perform a JOIN or get more data than the target entity normally has, it will just be loaded on the target entity, and no attempt will be made to map the data to other entities or to filter it based on only the defined fields.

Relations

Relations are convenient ways to access related, parent, and child entities from another loaded entity object. An example might be $post->comments to query for all the comments related to the current $post object.

Live Query Objects

All relations are returned as instances of relation classes that extend Spot\Relation\RelationAbstract. This class holds a Spot\Query object internally, and allows you to chain your own query modifications on it so you can do custom things with relations, like ordering, adding more query conditions, etc.

$mapper->hasMany($entity, 'Entity\Comment', 'post_id')
    ->where(['status' => 'active'])
    ->order(['date_created' => 'ASC']);

All of these query modifications are held in a queue, and are run when the relation is actually executed (on count or foreach iteration, or when execute is explicitly called).

Eager Loading

All relation types are lazy-loaded by default, and can be eager-loaded to solve the N+1 query problem using the with method:

$posts = $posts->all()->with('comments');

Multiple relations can be eager-loaded using an array:

$posts = $posts->all()->with(['comments', 'tags']);

Relation Types

Entity relation types are:

  • HasOne
  • BelongsTo
  • HasMany
  • HasManyThrough

HasOne

HasOne is a relation where the related object has a field which points to the current object - an example might be User has one Profile.

Method

$mapper->hasOne(Entity $entity, $foreignEntity, $foreignKey)
  • $entity - The current entity instance
  • $foreignEntity - Name of the entity you want to load
  • $foreignKey - Field name on the $foreignEntity that matches up with the primary key of the current entity

Example

namespace Entity;

use Spot\EntityInterface as Entity;
use Spot\MapperInterface as Mapper;

class User extends \Spot\Entity
{
    protected static $table = 'users';

    public static function fields()
    {
        return [
            'id'           => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
            'username'     => ['type' => 'string', 'required' => true],
            'email'        => ['type' => 'string', 'required' => true],
            'status'       => ['type' => 'integer', 'default' => 0, 'index' => true],
            'date_created' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }

    public static function relations(Mapper $mapper, Entity $entity)
    {
        return [
            'profile' => $mapper->hasOne($entity, 'Entity\User\Profile', 'user_id')
        ];
    }
}

In this scenario, the Entity\User\Profile entity has a field named user_id which the Entity\User's id field as a value. Note that no field exists on this entity for this relation, but rather the related entity.

BelongsTo

BelongsTo is a relation where the current object has a field which points to the related object - an example might be Post belongs to User.

Method

$mapper->belongsTo(Entity $entity, $foreignEntity, $localKey)
  • $entity - The current entity instance
  • $foreignEntity - Name of the entity you want to load
  • $localKey - Field name on the current entity that matches up with the primary key of $foreignEntity (the one you want to load)

Example

namespace Entity;

use Spot\EntityInterface as Entity;
use Spot\MapperInterface as Mapper;

class Post extends \Spot\Entity
{
    protected static $table = 'posts';

    public static function fields()
    {
        return [
            'id'           => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
            'user_id'      => ['type' => 'integer', 'required' => true],
            'title'        => ['type' => 'string', 'required' => true],
            'body'         => ['type' => 'text', 'required' => true],
            'status'       => ['type' => 'integer', 'default' => 0, 'index' => true],
            'date_created' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }

    public static function relations(Mapper $mapper, Entity $entity)
    {
        return [
            'user' => $mapper->belongsTo($entity, 'Entity\User', 'user_id')
        ];
    }
}

In this scenario, the Entity\Post entity has a field named user_id which is the Entity\User's id field's value. Note that the field exists on this entity for this relation, but not on the related entity.

HasMany

HasMany is used where a single record relates to multiple other records - an example might be Post has many Comments.

Method

$mapper->hasMany(Entity $entity, $entityName, $foreignKey, $localValue = null)
  • $entity - The current entity instance
  • $entityName - Name of the entity you want to load a collection of
  • $foreignKey - Field name on the $entityName that matches up with the current entity's primary key

Example

We start by adding a comments relation to our Post object:

namespace Entity;

use Spot\EntityInterface as Entity;
use Spot\MapperInterface as Mapper;

class Post extends Spot\Entity
{
    protected static $table = 'posts';

    public static function fields()
    {
        return [
            'id'           => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
            'title'        => ['type' => 'string', 'required' => true],
            'body'         => ['type' => 'text', 'required' => true],
            'status'       => ['type' => 'integer', 'default' => 0, 'index' => true],
            'date_created' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }

    public static function relations(Mapper $mapper, Entity $entity)
    {
        return [
            'comments' => $mapper->hasMany($entity, 'Entity\Comment', 'post_id')->order(['date_created' => 'ASC']),
        ];
    }
}

And add a Entity\Post\Comment object with a 'belongsTo' relation back to the post:

namespace Entity;

class Comment extends \Spot\Entity
{
    // ... snip ...

    public static function relations(Mapper $mapper, Entity $entity)
    {
        return [
            'post' => $mapper->belongsTo($entity, 'Entity\Post', 'post_id')
        ];
    }
}

HasManyThrough

HasManyThrough is used for many-to-many relationships. An good example is tagging. A post has many tags, and a tag has many posts. This relation is a bit more complex than the others, because a HasManyThrough requires a join table and mapper.

Method

$mapper->hasManyThrough(Entity $entity, string $hasManyEntity, string $throughEntity, string $selectField, string $whereField)
  • $entity - The current entity instance
  • $hasManyEntity - This is the target entity you want a collection of. In this case, we want a collection of Entity\Tag objects.
  • $throughEntity - Name of the entity we are going through to get what we want - In this case, Entity\PostTag.
  • $selectField - Name of the field on the $throughEntity that will select records by the primary key of $hasManyEntity.
  • $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->post_id field).

Example

We need to add the tags relation to our Post entity, specifying query conditions for both sides of the relation.

namespace Entity;

use Spot\EntityInterface as Entity;
use Spot\MapperInterface as Mapper;

class Post extends Spot\Entity
{
    protected static $table = 'posts';

    public static function fields()
    {
        return [
            'id'           => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
            'title'        => ['type' => 'string', 'required' => true],
            'body'         => ['type' => 'text', 'required' => true],
            'status'       => ['type' => 'integer', 'default' => 0, 'index' => true],
            'date_created' => ['type' => 'datetime', 'value' => new \DateTime()]
        ];
    }

    public static function relations(Mapper $mapper, Entity $entity)
    {
        return [
            'tags' => $mapper->hasManyThrough($entity, 'Entity\Tag', 'Entity\PostTag', 'tag_id', 'post_id'),
        ];
    }

Explanation

The result we want is a collection of Entity\Tag objects where the id equals the post_tags.tag_id column. We get this by going through the Entity\PostTags entity, using the current loaded post id matching post_tags.post_id.

Comments
  • Call For New Maintainer/Owner of Spot ORM

    Call For New Maintainer/Owner of Spot ORM

    Spot ORM needs a new owner

    Spot has a few issues and PRs piling up, and needs some love and attention.

    I no longer write PHP full-time (these days I am slinging JavaScript on frontend, Node.js, and mobile apps), so I am not using Spot every day like I used to when I made it. This means that I am always a few steps removed from the pain points, and I don't want Spot the project to die due to my own lack of use.

    So... who wants to assume ownership of Spot? I will transfer the repo, the packagist package, and the phpdatamapper.com website as well.

    The only criteria are:

    1. You are working in PHP with full time or near full time focus
    2. You currently use Spot in a project (preferably your main one you work with daily)
    3. You have contributed to Spot in some way (could just be reporting issues, fixing the docs, or facilitating discussion)
    opened by vlucas 20
  • Field defaults not used

    Field defaults not used

    Hi,

    First of all, thanks for this ORM. I needed something simple, non-intrusive, without annotations, and this delivers.

    So this issue is fairly simple, in the documentation (http://phpdatamapper.com/docs/entities/), we can see the 'status' field is using "'default' => 0" in its definition. so one would assume it's building an Entity with such fields sets values according to this field if existing.

    While this is the case when "'value' => 0" is set in the field definition, using 'default' doesn't seem to do much.

    My understanding would be 'default' sets field value on insert if value is missing (i.e. NULL) and required, and 'value' sets it on entity build.

    AFAIK, Entity::initFields() takes 'value' into account, and 'default' is used in Entity\Manager::fields() [206-207] to populate Entity\Manager::fieldDefaultValues, which is later used in Mapper::get() [518], but doesn't set any value on create.

    So my question is fairly simple: is 'default' really relevant, since in almost any use case, 'value' does the trick? It seems there is, as of now, no point in using it. And if it's not, my guess is it shouldn't be referenced in the documentation.

    opened by Arzaroth 17
  • Perform toArray on eager loaded relationships when running toArray on Entity

    Perform toArray on eager loaded relationships when running toArray on Entity

    When running toArray on an Entity, the eager loaded record(s) are never filled. With this update, if you have eager loaded a relationship using ->with, you will be able to run toArray from an entity and get the data from the relationship.

    opened by jakefolio 17
  • hasOne vs BelongsTo

    hasOne vs BelongsTo

    I took the advice and changed my belongsTo into a hasOne

    inside my client-entity:

    
     public static function fields()
        {
            return [
                'id'                        => ['type' => 'integer', 'primary' => true, 'autoincrement' => true],
                ...some more fields...
                'country_id'                => ['type'  => 'integer'],
                'crmdepartment_id'          => ['type'  => 'integer'],
                'name'                      => ['type'  => 'string'],
                'user1_id'                  => ['type'  => 'integer'],
                'user2_id'                  => ['type'  => 'integer'],
                ...some more fields...
                ];
        }
    
    

    And these are my relations:

        public static function relations(\Spot\MapperInterface $mapper, \Spot\EntityInterface $entity)
        {
            return [
                'country' => $mapper->hasOne($entity, 'Entities\Country', 'country_id'),
                'department' => $mapper->hasOne($entity, 'Entities\Department', 'crmdepartment_id'),
                'crmuser1' => $mapper->hasOne($entity, 'Entities\Crmuser', 'user1_id'),
                'crmuser2' => $mapper->hasOne($entity, 'Entities\Crmuser', 'user2_id'),
            ];
        }
    
    
    

    Everything was a belongsTo and was working fine. Now I have the hasOne... and it is broken. I get and sql error and these are the 2 slq commands when I debug:

    SELECT * FROM `client` `client` WHERE `client`.`crmdeletedby_id` = ? AND `client`.`crmdepartment_id` >= ? AND `client`.`clientstatus` = ? ORDER BY `client`.`name` ASC LIMIT 15 OFFSET 0 (Params: 0,0,100)
    SELECT * FROM `crmdepartment` `crmdepartment` WHERE `crmdepartment`.`crmdepartment_id` IN (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) (Params: 1,2,623,803,231,593,256,665,594,241,230,1153,1152,237,952)
    

    What I see, is that with the 2nd query, it takes the wrong field, because it should be 'id' and not 'crmdepartment_id'.

    Perhaps I still don't know the difference in belongsTo and hasOne, or I don't understand how it works.

    opened by marcelloh 16
  • Mapping column names to properties

    Mapping column names to properties

    Is there a way to map a column name to the object property name? I have an "employee_table" table with a PK of "employee_id" but want the proper in the Entity to just be "id", am I missing something or is that not available in Spot2?

    opened by smithtonson 15
  • ManyToMany relationship validator not validating

    ManyToMany relationship validator not validating

    Hey,

    I've been tinkering with the ManyToMany relationship and stumbled upon a problem:

    It seems that since I'm using an array, the validator doesn't work, i.e., no exception is thrown when inserting invalid values in the courses.

    Now, I'm not sure I have the "glue" Entity properly implemented, or if this is the expected behavior, but I think it makes sense to have this check.

    Trace:

    $config = new Spot\Config();
    $config->addConnection('mysql', [
        'driver'   => $settings['driver'],
        'host'     => $settings['host'],
        'dbname'   => $settings['database'],
        'user'     => $settings['username'],
        'password' => $settings['password'],
        'charset'  => $settings['charset']
        ]);
    $database = new Spot\Locator($config);
    
    $courseMapper = $database->mapper('Entities\Course');
    $proposalMapper = $database->mapper('Entities\Proposal');
    
    $course = $courseMapper->create([
        'name' => 'Something'
    ]); // I will assume this gets the id 1
    
    $proposal = $proposalMapper->build([]);
    $proposal->relation('courses', [2]); // Should throw validation exception or
    $proposalMapper->save($proposal); // Should throw validation exception
    

    Source (simplified):

    Proposal Entity

    <?php
    namespace Entities;
    
    use Spot\Entity;
    use Spot\MapperInterface;
    use Spot\EntityInterface;
    
    class Proposal extends Entity
    {
        protected static $table = 'proposals';
    
        public static function fields()
        {
            return [
                'id' => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
            ];
        }
    
        public static function relations(MapperInterface $mapper, EntityInterface $entity)
        {
            return [
                'courses' => $mapper->hasManyThrough(
                    $entity,
                    'Entities\Course',
                    'Entities\ProposalCourse',
                    'course_id',
                    'proposal_id'
                )
            ];
        }
    }
    

    Course Entity

    <?php
    namespace Entities;
    
    use Spot\Entity;
    
    class Course extends Entity
    {
        protected static $table = 'courses';
    
        public static function fields()
        {
            return [
                'id'       => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
                'name' => ['type' => 'text'],
            ];
        }
    }
    

    ProposalCourse Entity

    <?php
    namespace Entities;
    
    use Spot\Entity;
    
    class ProposalCourse extends Entity
    {
        protected static $table = 'proposalscourses';
    
        public static function fields()
        {
            return [
                'id'                 => ['type' => 'integer', 'autoincrement' => true, 'primary' => true],
                'proposal_id' => ['type' => 'integer'],
                'course_id'    => ['type' => 'integer'],
            ];
        }
    }
    

    Edit: added replication code. Edit2: fix typo in course id number. Edit3: add missing 'course_id' to ProposalCourse and remove field 'courses' from Proposal. Create relation using the relation method.

    opened by migueldemoura 14
  • Patch for foreign keys (empty variable bug, alias support) #77 #136

    Patch for foreign keys (empty variable bug, alias support) #77 #136

    Pull request to patch the addForeignKeys() function added in PR #136

    Issue 1: The $relations variable is empty and the migration fails (@Schrank could reproduce the problem). Issue 2: If your foreign key is mapped to an aliased column then the migration fails.

    Example Entity:

    class Example extends \Spot\Entity
    {
      protected static $table = 'example';
      public static function fields()
      {
        return [
          'id' => ['type' => 'integer', 'primary' => true, 'autoincrement' => true],
          'officeId' => ['type' => 'integer', 'required' => true, 'column' => 'office_id']
        ];
      }
      public static function relations(Mapper $mapper, Entity $entity)
      {
        return [
          'office' => $mapper->belongsTo($entity, 'Rendelo\Entity\Office', 'officeId')
        ];
      }
    }
    
    opened by FlipEverything 14
  • How to use fields instead of a *

    How to use fields instead of a *

    I am trying to select just some fields to reduce the data I have to put into my json result. I looked into building the query myself:

    $sql = "SELECT client.field1, client.field2, client.field3 FROM client where city='Amsterdam';
    $query = $this->query($sql);
    $result = $query($sql)
        ->with(['department', 'country'])
        ->order(['name' => 'ASC']);
    

    But this gives me an error: Call to undefined method Spot\Entity\Collection::with()

    and I can't seem to find in the docs, or in the code, how this should be done.

    I posted this by accident in the spot1 issues, but now in spot2. Vance already mentioned, that if I don't use the querybuilder, with doesn't work. So the final question would be: Can I get only fields instead of a asterix AND use the query builder?

    In my workaround, I just remove all unnecessary data from the result, but that takes time, which seems like a waist to me.

    opened by marcelloh 13
  • $cfg->addConnection unfriendly for DI containers like Aura DI 3.x

    $cfg->addConnection unfriendly for DI containers like Aura DI 3.x

    This is a great container but allows just one argument per parameter, meaning this mysql or whatever should go inside the array and addConnection should accept just an array. Of course, for compatibility reasons this form should also be kept, but we should allow just one array as an argument.

    $cfg->addConnection('mysql', [
        'dbname' => 'mydb',
        'user' => 'user',
        'password' => 'secret',
        'host' => 'localhost',
        'driver' => 'pdo_mysql',
    ]);
    

    becomes like:

    $cfg->addConnection([
        'name' => 'mysql',
        'dbname' => 'mydb',
        'user' => 'user',
        'password' => 'secret',
        'host' => 'localhost',
        'driver' => 'pdo_mysql',
    ]);
    

    and the contract should be like:

    /**
    * $name string|array
    */
    public function addConnection($name, $dsn = null, $default = false) {}
    

    In the future releases it should probably loose the other parameters and keep just the array (debatable).

    opened by acim 12
  • Problem updating entity with multiple primary key column

    Problem updating entity with multiple primary key column

    I have got problem with updating entity with multiple primary key column.

    Table definition

    public static function fields() {
            return [
                'storeNo' => ['type' => 'string','primary'=>true, 'required' => true],
                'weekday' => ['type' => 'integer','primary'=>true, 'required' => true],
                'open_hour' => ['type' => 'integer'],
                'close_hour' => ['type' => 'integer'],
            ];
        }
    

    When i try to update an entity using mapper update method it update all rows in databases with the same weekdDay. Mapper create such SQL:UPDATEopening_hoursSETopen_hour= ?,close_hour= ? WHEREweekday= ?. A storeNo key is missing in where clause.

    I know that, I can do update with raw sql, but i want to do this with orm funcionality :)

    opened by michalp82 11
  • Custom getter should be able to access the field directly

    Custom getter should be able to access the field directly

    When I define a getter like this:

    public function getSite()
    {
        $site = $this->Site;
    
        if (!preg_match("~^https?://.+$~", $site)) {
            return "http://" . $site;
        } else {
            return $site;
        }
    }
    

    And I try to access the Site property like this:

    $site = $object->Site;
    

    I get an exception inside the getter saying the Site property is undefined, even though it worked fine the first time ($object->Site), thanks to the __get magic method. Why is that?


    There is also, I think, a mistake in the __get magic method, as it sets $this->_inGetter[$field] = true while checking !in_array($this->_inGetter, $field). $field is put in the keys of the array, so it would not be found by the in_array function. I guess a !empty or array_key_exists would work.

    opened by sylbru 10
  • Support for php 8.1?

    Support for php 8.1?

    After upgrade php version to 8.1 I am getting errors such as Return type of Spot\Query::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice

    opened by nemanjaljuba 1
  • Search in some Entity

    Search in some Entity

    Hi! Please help me, how to search at the same time in some entities.

    my tables:

    items:

            'id' => ['type' => 'integer',  'autoincrement' => true, 'primary' => true, 'unsigned' => true, 'unique' => true, 'default' => null],   
            'status' => ['type' => 'integer', 'default' => 0],
            'avaible' => ['type' => 'integer', 'default' => 0],
            'created_by' => ['type' => 'integer', 'unsigned' => true, 'unique' => false, 'default' => null],
            'updated_by' => ['type' => 'integer', 'default' => null, 'unsigned' => true, 'unique' => false],
            'created_at' => ['type' => 'datetime', 'value' => new \DateTime()],
            'updated_at' => ['type' => 'datetime', 'value' => new \DateTime()],
    

    relations:

            'price' => $mapper->hasOne($entity, ItemPrice::class, 'item')->order(['created_at' => 'DESC']),
            'image' => $mapper->hasOne($entity, Images::class, 'item'),
            'hu' => $mapper->hasOne($entity, ItemContent::class, 'item')->where(['lang' => 'hu']),
            'en' => $mapper->hasOne($entity, ItemContent::class, 'item')->where(['lang' => 'en']),
            'item_box' => $mapper->belongsTo($entity, ItemBox::class, 'box'),
            'categories' => $mapper->hasManyThrough($entity, Category::class, ItemCategory::class, 'category_id', 'item_id'),
            'created_user' => $mapper->belongsTo($entity, Admin::class, 'created_by'),
            'updated_user' => $mapper->belongsTo($entity, Admin::class, 'updated_by'),
            'search' => $mapper->hasOne($entity, ItemTitleSearch::class, 'item'),
            'getitem' => $mapper->hasOne($entity, ItemContent::class, 'item'),
    

    item_contents:

            'id' => ['type' => 'integer', 'autoincrement' => true, 'primary' => true, 'unsigned' => true, 'unique' => true],
            'item' => ['type' => 'integer', 'default' => null, 'unsigned' => true, 'unique' => false],
            'lang' => ['type' => 'string', 'required' => true],
            'title' => ['type' => 'string', 'required' => true],
            'description' => ['type' => 'text', 'default' => '']
    

    relations:

           'hu' => $mapper->belongsTo($entity, Items::class, 'item'),
           'en' => $mapper->belongsTo($entity, Items::class, 'item'),
    

    item_categories:(manytomany), hasManyThrough

            'id' => ['type' => 'integer', 'autoincrement' => true, 'primary' => true, 'unsigned' => true, 'unique' => true, 'default' => null],
            'item_id' => ['type' => 'integer', 'default' => 0, 'unsigned' => true, 'unique' => false],
            'category_id' => ['type' => 'integer', 'default' => 0, 'unsigned' => true, 'unique' => false],
    

    relations:

            'item' => $mapper->belongsTo($entity, Items::class, 'item_id'),
            'category' => $mapper->belongsTo($entity, Category::class, 'category_id'),
    

    category:

            'category_id' => ['type' => 'integer', 'default' => 0,'primary' => true, 'unsigned' => true, 'unique' => true],
            'image' => ['type' => 'string'],
            'parent_id' => ['type' => 'integer', 'default' => 0, 'unsigned' => true, 'unique' => false],
            'status' => ['type' => 'integer', 'default' => 0],
    

    relations:

            'hu' => $mapper->hasOne($entity, CategoryDescription::class, 'category_id')->where(['language_id' => 3]),
            'en' => $mapper->hasOne($entity, CategoryDescription::class, 'category_id')->where(['language_id' => 1]),
            'parentcategory' => $mapper->belongsTo($entity, Category::class, 'parent_id'),
            'items' => $mapper->hasManyThrough($entity, Items::class, ItemCategory::class, 'category_id', 'item_id')
    

    ItemTitleSearch:

            'id' => array('type' => 'integer', 'serial' => true),
            'item' => array('type' => 'integer', 'primary' => true,'index' => true, 'required' => true),
            'title' => array('type' => 'text', 'required' => true, 'fulltext' => true),
    

    relations:

            'getitem' => $mapper->hasOne($entity, Items::class, 'id'),
    

    so, a make a search:

    $searchText = 'stringintext';
    $allItems = $this->entity->mapper(Items\ItemTitleSearch::class);
    $allItems = $allItems->where(['title :like' => '%' . $allItems . '%'])
    
    

    its working good!

    But i want search in titles and categories, how can i do it? Thanks!

    opened by biohardware 0
  • Prevent some query methods to break relations

    Prevent some query methods to break relations

    If one uses methods from the Query class that don't return the Query object through __call when dealing with a relation object, it becomes unusable.

    This PR aims to fix that.

    The test provided illustrate one of the cases in which this could happen (namely, ordering a hasMany relation before retrieving the first item).

    opened by Arzaroth 0
  • FIx memory leak in Entity

    FIx memory leak in Entity

    This fixes a memory leak in the Entity class. In my case, this represented ~3kB per Entity (which has around 40 relations). I don't think tests are needed for this PR.

    opened by Arzaroth 1
  • Ordering in hasManyThrough relation doesn't work

    Ordering in hasManyThrough relation doesn't work

    I have ProjectEntity with such relation in it 'users' => $mapper->hasManyThrough($entity, 'Api\V2\Entities\UserEntity', 'Api\V2\Entities\UserProjectEntity', 'user_id', 'project_id')->active()->order(['displayname' => 'ASC']) ->order() does not affect on the result, its sorted by id of UserProjectEntity (it has simple structure id - user_id -project_id)

    bug needs tests 
    opened by bbrody 2
Releases(v2.2.1)
  • v2.2.1(Nov 28, 2018)

    Major Changes:

    • Add $mapper->exec() method for raw queries (@vlucas)
    • Add multiple compound index support (@splio-aleroux)
    • Add multiple compound unique index support (@FlipEverything)
    • Field default (if given) is now used as default value (#215) (@Arzaroth)
    • Add multiple database config support (@dasper)
    • Fix for field alias mapping bug (#237, #145) (@FlipEverything)
    • Add function support to ordering and grouping (@FlipEverything)

    Announcements:

    • In the next minor release (v2.3) we will drop PHP<5.6 support.
    • We are thinking about dropping the currently unofficial HHVM support and focus our efforts on PHP7+
    Source code(tar.gz)
    Source code(zip)
  • v2.2.0(May 21, 2016)

    Major Changes:

    • Scopes enhancements - scopes can now be used directly off relation names (#135) (thanks @nebulousGirl)
    • Foreign key support for relations (thanks @nebulousGirl)
    • Automatic saving, updating, and deleting of related entities with the optional $mapper->save($entity, ['related' => true]) flag (thanks @nebulousGirl)
    • Add support for column aliases to foreign keys (#157) (thanks @FlipEverything)
    • Custom Query Operator support (#161) (thanks @marcojetson)
    Source code(tar.gz)
    Source code(zip)
  • v2.1.7(Sep 9, 2015)

    Major Changes:

    • Support for column aliases with the new column option in the Entity fields definition
    • Added better quoting of fields and table names for all queries (thanks @potfur)
    • Various style fixes and cleanup work (thanks @localheinz)
    Source code(tar.gz)
    Source code(zip)
  • v2.1.6(Jul 10, 2015)

    • Added 'afterLoad' event that fires with fully loaded entities (thanks @kayladnls)
    • General cleanup of .travis.yml for Ci builds (thanks @localheinz)
    • Limit depth of toArray with relations to single level by default (thanks @jakefolio)
    • Added PHP 7.0 in Travis Ci build
    Source code(tar.gz)
    Source code(zip)
Simple async lowlevel ICMP (ping) messaging library built on top of React PHP

clue/icmp-react Simple async lowlevel ICMP (ping) messaging library built on top of react Usage Once clue/icmp-react is installed, you can run any of

Christian Lück 13 Jun 10, 2022
Tiny, fast and simple PHP boilerplate built on top of FlightPHP

BlessPHP Tiny, fast and simple PHP boilerplate built on top of FlightPHP. Good enough to use as skeleton for prototypes and some pet-projects. The nam

Anthony Axenov 2 Sep 20, 2022
Clock - A PHP 7.0 compatible clock abstraction

Clock - A PHP 7.0 compatible clock abstraction

kreait 88 Oct 19, 2022
This component, based on the Symfony serializer and async-aws, is a human-readable and quick abstraction to easily store serialized objects in DynamoDB 🚀.

DynamoDB Storable This component, based on the Symfony serializer and async-aws, is a human-readable and quick abstraction to easily store serialized

Matthieu W. 2 Jun 19, 2022
Google Tag Manager for Magento 2 with Advance Data Layer

Google Tag Manager is a user-friendly, yet powerful and cost-effective solution that is a must-have integration for every Magento store. It simplifies the process of adding and managing third-party JavaScript tags. With dozens of custom events and hundreds of data points our extensions the #1 GTM solution for Magento.

MagePal :: Magento Extensions 241 Dec 29, 2022
PHP Runtime Layer for AWS Lambda

PHP Layer For AWS Lambda Ever wanted to run PHP websites in AWS Lambda? It's your lucky day! This Lambda Runtime Layer runs the PHP 7.3/7.1 webserver

Stackery 319 Nov 30, 2022
Envbar allows you to differentiate between environments by adding a custom colored bar above the top navigation.

Envbar Envbar allows you to differentiate between environments by adding a custom colored bar above the top navigation. This should help backend users

Magenizr 6 Oct 7, 2022
WordPlate is a wrapper around WordPress. It makes developers life easier. It is just like building any other WordPress website with themes and plugins. Just with sprinkles on top.

WordPlate is simply a wrapper around WordPress. It makes developers life easier. It is just like building any other WordPress website with themes and plugins. Just with sprinkles on top.

WordPlate 1.7k Dec 24, 2022
Buy and sell crypto top 100 crypto with our fake currency. Donate to and use our referal links for discounts

PLEASE ENABLE SQLITE3 AND GD OR GD2 IN XAMPP TO RUN THE APP! (SEE HOW_TO_SETUP_XAMPP.gif) ![alt text](https://github.com/Tby23rd/Project1-Cryptosimul

Tabitha Maru 0 Dec 26, 2021
Messaging solutions for PHP - It contains advanced features build on top of a transport component

It contains advanced features build on top of a transport component. Client component kind of plug and play things or consumption component that simplify message processing a lot. Read more about it in documentation.

Enqueue 175 Jan 3, 2023
Adds support for quickly adding a "snow day" banner at the top of a website.

❄️ Snow Day Plugin ?? Requires OctoberCMS 2.0 ✨ What does this plugin do? Provides the ability to quickly add a cross-site banner using a component. ❓

Albright Labs 4 Nov 7, 2022
Here is the top 100 PHP functions: it is the list of the most often used PHP native functions

Here is the top 100 PHP functions: it is the list of the most often used PHP native functions. If you are a PHP developer, you must know the Top 100 PHP Functions deeply.

Max Base 16 Dec 11, 2022
Description: A simple plugin that sets the current admin page to always be at the top of the admin menu.

=== Sticky Admin Menu === Contributors: sc0ttkclark Donate link: https://www.scottkclark.com/ Tags: admin menu, sticky Requires at least: 4.4 Tested u

Scott Kingsley Clark 2 Sep 29, 2022
A web application built on PHP for user to view their credit information in their mysql database

TheCreditInfo Table of Content About Inspiration Technologies Client Pages Usage About Credere is a website created to help you track your credit hist

Abdul-Baseet Shabi 0 Jul 21, 2022
This project processes a small database with php all on a web server. This project uses XAMPP to run the web server and the database.

PHP-introduction This project processes a small database with php all on a web server. This project uses XAMPP to run the web server and the database.

Tyler Jacques 1 Jan 6, 2022
A PHP MySQL database client class to simplify database access

This lightweight database class is written with PHP and uses the MySQLi extension, it uses prepared statements to properly secure your queries, no need to worry about SQL injection attacks.

Khader Handal 50 Jul 30, 2022
Bug bounty tools built in PHP to help penetration tester doing the job

BugBountyTools-PHP Bug bounty tools built in PHP to help penetration tester doing the job Website who using this script: KitaBantu Soon! 403 Bypasser

Muhammad Daffa 7 Aug 17, 2022
Tutorial app which is built in the tutorial

Nextcloud App Tutorial This is the tutorial app which shows how to develop a very simple notes app. Try it To install it change into your Nextcloud's

Nextcloud 43 Dec 2, 2022
A Symfony bundle built to schedule/consume repetitive tasks

Daily runs Code style Infection PHPUnit Rector Security Static analysis A Symfony bundle built to schedule/consume repetitive tasks Main features Exte

Guillaume Loulier 98 Jan 4, 2023