Giter Club home page Giter Club logo

atlas.orm's Introduction

Atlas.Orm

Atlas is a data mapper implementation for persistence models (not domain models).

As such, Atlas uses the term "record" to indicate that its objects are not domain objects. Use Atlas records directly for simple data source interactions at first. As a domain model grows within the application, use Atlas records to populate domain objects. (Note that an Atlas record is a "passive" record, not an active record. It is disconnected from the database.)

Documentation is at http://atlasphp.io.

atlas.orm's People

Contributors

adamculp avatar andrewshell avatar aretecode avatar b1rdex avatar dhaaker avatar fadoe avatar froschdesign avatar garak avatar harikt avatar jakejohns avatar jblotus avatar jelofson avatar karlomikus avatar kenjis avatar pmjones avatar willemwollebrants avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

atlas.orm's Issues

Missing get PDO connection from Atlas ORM

I have an instance of Atlas\Orm\Atlas object and I pass this object to constructors of different classes which need to do something with database. But sometimes these classes need to execute raw SQL query because it's too complex for Atlas ORM or not supported syntax. So I need to pass into costructor both Atlas ORM object and PDO object. This is not convenient.

It will be just excellent if Atlas ORM will be able to give its PDO (or Atlas\Pdo\Connection) object for some low-level queries. Currently access to PDO from Atlas is prohibited.

[Docs] Missing database model

Problem

In the documentation is used the database with name testdb. In many description these tables are used:

  • threads
  • author
  • summary
  • replies
  • taggings
  • tags
  • comments

But nowhere we can find the database model or any other introduction to this example database.

Suggestion

Add a graphical database model to illustrate the tables, columns and relations. (Optimal would we be the SVG format.)

Relationships

Hi Paul,

I came across a strange issue in relationship. Was trying to write a unit test, but reading https://github.com/atlasphp/Atlas.Orm#relationships I got a bit confused so thought of discussing the same.

We have an articles table and users table.

Articles Table

id user_id title body
1 1 something something
2 1 another another

Users Table

id name
1 James
2 Bond

The mapper was defined as

class ArticleMapper extends \Atlas\Orm\Mapper\AbstractMapper
{
    protected function setRelated()
    {
        $this->manyToOne('user', UserMapper::CLASS);
        //    ->on([
        //        'user_id' => 'id'
        //    ]);
    }
}

class UserMapper extends \Atlas\Orm\Mapper\AbstractMapper
{
    protected function setRelated()
    {
        $this->oneToMany('articles', ArticleMapper::CLASS); 
        // added on later
    }
}

Now when you fetch article id 2 it returns Bond as user, but it should return James instead.

$record = $atlas->fetchRecord(ArticleMapper::CLASS, 2, ['user']);
echo $record->user->name;

When we add on it works as expected. I noticed when we change article id to 3, it tries to fetch user with id 3. Is that expected ? That is where I got confused with the docs https://github.com/atlasphp/Atlas.Orm#relationships

By default, in all relationships except many-to-one, the relationship will take the primary key column(s) in the native table, and map to those same column names in the foreign table. In the case of many-to-one, it is the reverse; that is, the relationship will take the primary key column(s) in the foreign table, and map to those same column names in the native table.

Thank you

SQL Error on ManyToMany

Using the scenario of threads, tags, and taggings, and we try the following:

$post = $atlas->fetchRecord(ThreadMapper::CLASS, 2, ['taggings', 'tags']);

If there are no associated tags for the post, then the query will fail (in mysql). Works in sqlite, so tests seem to pass.

What appears to happen is the 3rd query that is prepared will look something like this:

SELECT
    `tags`.`tag_id`,
    `tags`.`tag`
FROM
    `tags`
WHERE
    `tags`.`tag_id` IN ()

and MySQL will throw a syntax error (NOTE that sqlite does not return an error here)

PDOException: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 7 in /home/jelofson/www/atlas/vendor/aura/sql/src/ExtendedPdo.php:748

Perhaps the query for tags should be issued, given that there are no taggings returned.

Ordering on a many-to-many

So, it is possible to modify the select via a closure when returning related records.

For example:

<?php
// fetch thread_id 1; with only the last 10 related replies in descending order;
// including each reply author
$threadRecord = $atlas->fetchRecord(
    ThreadMapper::CLASS,
    '1',
    [
        'author',
        'summary',
        'replies' => function ($selectReplies) {
            $selectReplies
                ->limit(10)
                ->orderBy(['reply_id DESC'])
                ->with([
                    'author'
                ]);
        },
    ]
);

However, when you have a many-to-many through an association, any ordering on the many-to-may is ignored. This is because the stitching loops through the association records first, performs a "match" and then adds the foreign records.

Consider this fetch:

<?php
$author = $this->atlas->fetchRecord(AuthorsMapper::class, $id, [
            'author_publications',
            'publications'=>function ($select) {
                $select->orderBy(['title ASC']);
            }
        ]);

My suggestion, and I tested this a bit, is to loop through the foreign records first, and then check those against the through records. See below from ManyToMany.php

<?php
    protected function getMatches(RecordInterface $nativeRecord, array $foreignRecords)
    {
        $matches = [];

        /* Don't loop through association first.
        foreach ($nativeRecord->{$this->throughName} as $throughRecord) {
            foreach ($foreignRecords as $foreignRecord) {
                if ($this->recordsMatch($throughRecord, $foreignRecord)) {
                    $matches[] = $foreignRecord;
                }
            }
        }
         * 
         */
        // Loop through the foreigns and append to matches in the order they are already in.
        foreach ($foreignRecords as $foreignRecord) {
            foreach ($nativeRecord->{$this->throughName} as $throughRecord) {
                if ($this->recordsMatch($throughRecord, $foreignRecord)) {
                    $matches[] = $foreignRecord;
                }
            }
        }
        return $matches;
    }

What do you think?

Missing where method in Atlas class

Hello, and great work guys!
I read documentation last night and I saw this:
http://atlasphp.io/mapper/2.x/reading.html

$threadRecord = $atlas
    ->select(ThreadMapper::class)
    ->where('thread_id = ?', '1')
    ->fetchRecord();

After that I try to make a simple select with where statements and found that where function is missing from Atlas class. Currently I am using atlas/orm": "^2.2 in my composer.json file.

Possible [Bug] when handling transactions

I've just started with Atlas so forgive me if I'm missing something but I have a problem with transactions, I don't think they are working as intended.

I have a simple example, an Invoice with many child Items which I want to save inside a transaction. This is happening when I'm creating dummy invoices in a loop, the data is generated with Faker, the first create fails as expected, no items and no invoice but the subsequent invoices are created when the invoice items are not.

$invoice = $invoice_repository->create($data);

Here is some example input:

Array
(
    [customer_id] => 20
    [date] => 2017-12-23
    [reference] => 47213
    [items] => Array
        (
            [0] => Array
                (
                    [description] => Non ex officiis et incidunt dolore repellendus a.
                    [quantity] => 5
                    [price] => 1808.49
                )

            [1] => Array
                (
                    [description] => Occaecati dolorem ratione suscipit eum minima porro dolore quidem.
                    [quantity] => 9
                    [price] => 1380.57
                )

            [2] => Array
                (
                    [description] => Non animi numquam quae maiores.
                    [quantity] => 3
                    [price] => 959.32
                )
        )
)

This data is passed to the create function in InvoiceRepository.php, this code works fine but may not be optimal as I couldn't find a way to get persist() to save the invoice items directly from the array, they had to be converted to a recordSet:

// InvoiceRepository.php

    public function create(Array $data=[])
    {
        // begin transaction
        $transaction = $this->atlas->newTransaction();
        
        // create invoice from data, stripping out related child ['items'] from $data
        $invoice = $this->atlas->newRecord(InvoiceMapper::class, array_diff_key($data, ['items'=>0]));

        // create invoice items as record set
        $invoice->items = $this->atlas->newRecordSet(ItemMapper::class);
    
        // append item children from $data['items'] to $invoice->items
        foreach ($data['items'] as $item_data) {
            $invoice->items[] = $this->atlas->newRecord(ItemMapper::class, $item_data);
        }

        // store invoice and items
        $transaction->persist($invoice);
        
        // execute transaction and handle success/failure
        if ($transaction->exec()) {
            
            echo "OK";
        
        } else {

            // get the exception that was thrown in the transaction
            $e = $transaction->getException();

            // get the work element that threw the exception
            $work = $transaction->getFailure();

            // some output
            echo "The Transaction failed: ";
            echo $work->getLabel() . ' threw ' . $e->getMessage();
            
        }
        
    }

However once I added an event which threw an Exception I noticed the 'invoice' was created even though the 'invoice items' weren't created and the Exception was raised showing the "The Transaction failed" message.

// ItemMapperEvents.php

    public function beforeInsert(MapperInterface $mapper, RecordInterface $record)
    {
        throw new Exception("");
    }

Unless I have the code in the InvoiceRepository create function wrong I'd expect both the 'invoice' and 'invoice items' not to be created.

Here's my versions:

atlas/cli 1.0.1
atlas/orm 2.1.0
aura/cli 2.2.0
aura/sql 2.5.1
aura/sqlquery 2.7.1
aura/sqlschema 2.0.3 

Relationships manyToOne NULL

Hi,

I have a problem with relationships manyToOne with capacity to be null.

The relation key return FALSE if the relation is NULL. I can understand that.
But the problem, it's with Atlas.Transit, the Transit object throw an exception.

For example :

Entities:

  • class Thread
  • class Author

Thread can have an author or anonymous (without author).
In the case of anonymous. The field "author" is FALSE instead NULL.
Atlas.Transit works fine with existent relation, but throw an exception if relation is NULL:

TypeError: Argument 2 passed to Atlas\Transit\Transit::newDomainEntity() must be an instance of Atlas\Mapper\Record, boolean given

Thanks.

Issue with character encoding

Hello, I think there is a bug with fetching records with character encoding different that UTF-8.

Package version:

atlas/cli                1.1.0
atlas/orm             2.5.0
aura/cli                2.2.0
aura/sql               2.6.0
aura/sqlquery      2.7.1
aura/sqlschema   2.0.3

In database I have record like this: "Bayern München"
When I use native PDO query the result is the same, but when I query it through orm I get this: Bayern M�nchen

My table schema is:

CREATE TABLE `teams_i18n` (
  `title` varchar(255) CHARACTER SET latin1 NOT NULL
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

Should many-to-many be discouraged?

This issue is born of my work on the fix-record-keys branch in relation to issue #55.

tl;dr: Should many-to-many relationships be downplayed, even discouraged, in favor of one-to-many and many-to-one relationships?


Atlas supports a "many-to-many" type of relationship. For example, if you have Threads and Tags, then each Thread might have many Tags on it, and likewise each Tag might apply to many Threads. This is defined in the database using an association table ("Taggings") that maps the many Thread IDs to many Tag IDs, and modeled in Atlas with one Thread & Tag to many Taggings, and many Taggings to one Thread & Tag.

The end result of the many-to-many is to make it look like the Thread has Tags on it, but that's not actually the case. The Thread has Taggings, and the Taggings have the Tags. So the Tags are attached to the Thread only indirectly.

Now, what's interesting here is that automatically setting the native record keys on a many-to-one, or setting the foreign record keys on a one-to-one or one-to-many, is straightforward. But it seems like you never actually need to set anything on the "far" side of many-to-many relationships, since everything is handled as part of the "association" ("through") portion of the relationships.

Further, if you have a many-to-many relationship and you add a new record to it, you need to manage two related fields: the many-to-many record set, and the "association" record sets. That is, if you create a new Tag and want to attach it to a Thread, you need to do something like this:

$thread->taggings->appendNew([
    'thread' => $thread,
    'tag' => $tag,
]);

$thread->tags[] = $tag;

With all that in mind, it occurs to me that having an explicit many-to-many relationship is convenient, but might have been a misstep. Instead, perhaps the thing to do is downplay the existence of many-to-many, discourage its use, and emphasize the actual one-to-many and many-to-one relationships in their place.

For example, right now you would do this to get the Tags on a Thread:

$thread = $atlas->fetchRecord(ThreadMapper::CLASS, 1)
    ->with([
        'taggings',
        'tags',
    ]);

foreach ($thread->tags as $tag) {
    echo $tag->name;
}

If the many-to-many relationship is downplayed, you would do this instead:

$thread = $atlas->fetchRecord(ThreadMapper::CLASS, 1)
    ->with([
        'taggings' => [
            'tag',
        ],
    ]);

foreach ($thread->taggings as $tagging) {
    echo $tagging->tag->name;
}

A little more verbose, but very clear on what is related to what (i.e., that the Thread owns Taggings, and the Taggings own the Tags).

This would also mean managing only one related, not two, when attaching a new Tag:

$thread->taggings->appendNew([
    'thread' => $thread,
    'tag' => $tag,
]);

// no $thread->tags to deal with

This downplaying/discouraging of many-to-many does not require any code changes. It will be more of a documentation and suggested practices thing.

Users of Atlas, what are your thoughts on this?

Persisting the manyToMany after the native record

When I append a new "taggings" to a post, should the post's "tags" also be updated?

$post = $this->atlas->fetchRecord(PostsMapper::class, 1, ['postsTags', 'tags']);
$tag = $this->atlas->fetchRecord(TagsMapper::class, 3);
// There are no tags or posts_tags at this point
$post->tags = $this->atlas->newRecordSet(TagsMapper::class);
$postsTags = $this->atlas->newRecordSet(PostsTagsMapper::class);
$postsTags->appendNew([
    'post'=>$post,
    'tag'=>$tag
]);
        
$post->postsTags = $postsTags;

var_dump($this->atlas->persist($post));
// This foreach will not list the newly added tag unless you forcibly assign $post->tags[] = $tag
foreach ($post->tags as $t) {
    echo $t->tag;
}

The $post->postsTags is accurate, of course. I have no problem accessing the tags data this way, but should the tags recordset also be updated after persisting the native record? This does get back to the idea of discouraging the direct access to the manyToMany in favour of the oneToMany etc.

Thoughts?

Idea: Connection configuration on table level

@pmjones @harikt

just an idea
we don't have a project with enough load to justify such feature.
however it is reasonable design concern when a project producer chooses ORM library that can distributing database load

IF a site become busy, it will need:

  1. split some tables into a different servers, for example
    -- sessions table if session is implemented with database
    -- i think the best place is to have getConnection() inside each table, when missing, it will fall back to default
  2. split read and write connection, so each table may also need getReadConnection, getWriteConnection

The idea would be that when a project needs to move for example sessions table to a different host, it would add the following:

SessionTable {
     public function getConnectionName() {
          return 'new-host-connection';  // as "key" for $connectionLocator
    }
}

Thoughts on returning an empty RecordSet vs empty array

This is really just about me being a bit lazy, but I have run into this a couple of times where returning an empty RecordSet might be beneficial.

Case 1 - appending a related

$post = $atlas->fetchRecord(PostsMapper::class, 2, ['taggings', 'tags']);
$tag = $atlas->fetchRecord(TagsMapper::class, 3); // or it could be a new one

// There are no post->taggings in the DB yet.

// You must create a new recordset before appending because $post->taggings = []
if (! $post->taggings) {
    $post->taggings = $atlas->newRecordSet(TaggingsMapper::CLASS);
}
$post->taggings->appendNew([
    'post'=>$post,
    'tag'=>$tag
]);

Case 2 - Custom methods on the RecordSet object

$authors = $atlas->select(AuthorsMapper::CLASS)->fetchRecordSet();
// You have a custom method in the AuthorsRecordSet class that does something.
// If there are no authors yet, then you need to create a new record set
if (! $authors) {
    $authors = $atlas->newRecordSet(AuthorsMapper::CLASS);
}
$foo = $authors->myMethod();

If fetchRecordSet() always returned a record set, even if empty, then you wouldn't need that additional check etc.

What do you think? I am not opposed to the empty array, but it does mean a couple of extra steps on occasion.

[RELATIONSHIPS] Duplicate records with related relationships

Hello, good afternoon.

I have a problem using the with method when getting relationships with the fetchRecordSet.

screen shot 2017-12-15 at 17 08 06

I have a nested code like that:

$operation = $atlas->select(OperationMapper::class) ->with([ 'credit' => [ 'credit_movement' => [ 'creditAttachement', 'attachment', ] ], ]) ->fetchRecordSet();

The result show two records, after we use the getArrayCopy method:

  1. In the first record, the object attachment came with two positions on array;
  2. In the second record, the object attachment shows an empty array;
  3. But both of records has one record each on Attachment object.

How we can solve this problem?

How to persist changes back to the storage layer

I'm not really sure if this is the right place to ask this, as it isn't exactly an issue with the package per se, but it is something that I've been wondering since coming across this a few days ago.

When mapping your persistence model to your domain model (following the example set out here: https://github.com/atlasphp/Atlas.Orm/blob/1.x/docs/domain.md#map-from-persistence-to-domain), how do you go about persisting changes to the domain model back to the storage layer?

For example, assume there's a class in your domain model that accepts many properties through its constructor, and has some methods that mutate several properties of the object at once. This class doesn't expose all of its properties through getter methods, to avoid it being abused somewhere else in the application.

Now that I've finished mutating an instance of this class, I pass it back to the repository to be persisted. How does the repository get the data out of the object? Should it use reflection? Should the domain model class provide getter methods that aren't part of an interface, and trust that they won't be abused?

ManyToMany and Fetching Relateds

This is more of a question/confirmation of behaviour than an issue.
Consider the ubiquitous blog example of a posts table having many tags through a taggings association table. With the relationships defined, if you want to fetch the tags for a given post, you have to explicitly pass both the taggings relationship and the tags relationship to the fetch method. Is this correct? My original assumption was that I would only have to pass the tags relationship and atlas would perform the join magic based on the relationship definition. This did not seem to work in my testing. Can you confirm that the association table must also be included? Or do I have something configured poorly?

For example:

$post = $atlas->fetchRecord(
    Post::class, 
    2, 
    [
        'taggings', // Is this necessary?
        'tags'
    ]
);

question: removeRedundantData() or prepareColsVals() ?

Hi Paul

Assume you want to use property accessor

$user->name = "joe doe";
$user->non_existing_field = 'just a test';
$atlas->update($user); // ---- need to remove the "non_existing_field" before commit to storage

Do you think this should be built into Atlas OR application should build this themselves with Events ?

patch(record, data) or merge(record, data) + events

Hi Paul,

in apps, there are normally 2 types of updates:

  1. explicit fields update
    $user->name = "joe doe";
    $atlas->update($user);
  2. implicit / fuzzy fields update
    $atlas->patch($user, $data);
    $atlas->update($user);

I think the patch probably should have some MapperEvents methods, but no need for TableEvents methods, any thought ?

Increase Priority of Domain Example in TODO

I see in TODO listed under "Unknown Priority".

Add examples on how to properly wrap a Record in the Domain.

This is my official request to increase that priority. :-)

Having even an incomplete or very-subject-to-change write up on best practices/intended-usage/big-picture stuff I think would make it easier to contribute and identify or solve problems.

primary protection on fetched record

// assume a fetched $user with primary key id = 1
$user->id = 2;   // should not change the value

single or composite primary key should not be modified on a fetched record

__toString of select

Hi Paul,

I noticed the __toString() method returns a

Fatal error: Method Atlas\Orm\Mapper\MapperSelect::__toString() must not throw an exception

The query is simple as in

$threadRecordSet = $atlas
    ->select(ThreadMapper::CLASS)
    ->orderBy('thread_id DESC')
    ->limit(10);

The problem I found is with the Aura.SqlQuery throwing due to no column names present. I will look more closely and send a PR to fix the same.

Thank you

Idea: allow syntax -- new Article ( $initial_data )

@pmjones @harikt

Is it possible to have the following syntax:

$new_article = new Article( $initial_data );
$atlas->persist( $new_article );

Sure, it can be done like:

$new_article = $atlas->newRecord(\PathTo\ArticleMapper, $initial_data);
$atlas->create($new_article)

But the former would be a lot easier to read.

This idea is conflicting with current Record API
`Record :: __constructor(mapperClassName, $row, $related)

But we can probably achieve this via a small change:

Record :: __constructor($row_or_data, array $related = [], $mappeClassName = NULL);
-- if $row_or_data is not instance of ROW, new row($data)
-- we can generate the mapperClassName when provided NULL
-- this has only impact on "newRecord()" case, because existing record is acquired via "fetchRecord"

Idea: Row::$pseudo

@pmjones @harikt

this is related to row/record property value assignment
the idea is that, we CAN allow user to add additional properties

$user->full_name = $user->first_name .' '. $user->last_name; // assume full_name is not a field

we can check against "cols" + "related", on miss, we add the full_name to "pseudo" data array

Question: Where is the best place to convert php values to database values?

In my application I work with UUIDs. In the database table this values are saved as binary(16). In doctrine I can write a custom field type like this:

namespace Infrastructure\Persistence\Doctrine\Type;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\StringType;
use Domain\Station\Model\StationId;
use Ramsey\Uuid\Uuid;

class StationIdType extends StringType
{
    public function convertToPHPValue($value, AbstractPlatform $platform): ?StationId
    {
        return $value ? StationId::fromString(Uuid::fromBytes($value)->toString()) : null;
    }

    public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
    {
        return $value instanceof StationId ? Uuid::fromString((string) $value)->getBytes() : null;
    }

    public function getName(): string
    {
        return 'station_id';
    }
}

Where is the best place to add this in atlas php?

orderBy missing

Hi!
In docs there is $atlas->select([])->orderBy(...).
But in the srcs in Atlas class select method returns MapperSelect instance which has not orderBy method.
This is not correspond to docs. And I cant find an "Atlas way" to make a query with ordering.

Wrong parameter in AbstractTable::insertRowPerform() for lastInsertId method at PostgreSQL

At a PostgreSQL database the name of a sequence object is required for PDO::lastInsertId. At the moment the name of the autoincrement column is used.

$autoinc = $this->getAutoinc();
if ($autoinc) {
$row->$autoinc = $connection->lastInsertId($autoinc);
}

Changing the getAutoinc method in the specific table class doesn't work, because the method AbstractTable::insertRowPrepare() needs the name of the autoincrement column.

$autoinc = $this->getAutoinc();
if ($autoinc) {
unset($cols[$autoinc]);
}

Optimize Complex Structures

I'm working on a site that has a very complex interrelated database structure.

https://github.com/andrewshell/pen-paper-2/tree/0.1.0

Here is the most complex query in my application:
CreatorsAtlasRepository::getCreatorById

The complexity comes in the creator join tables. They can't really be manyToMany because there are multiple relationships off of them.

Creator─RpgBookCreator─┬─RpgBook─┬─Publisher
                       │         │
                       │         └─GameLine
                       │
                       └─Credit

In this particular query, Publisher is a sub-table under 4 different tables. GameLine is also under 4 different tables.

In total, there are 20 different tables that will need to be queried in order to assemble this structure.

I installed DebugBar and on a page like Creator Monte Cook (username pen-paper & password hoopla), it shows that there were 484 queries. Many of those are selecting single rows from publishers or game_lines.

I understand that this is an extreme example of a query with Atlas and completely understand if your response is "This is a weird edge case that I'm not supporting".

But since Paul is the author of "Solving The N+1 Problem In PHP" I thought he'd want to know. ;-)

I would also like to note that even with all those queries the page (with query cache disabled) renders in about 1.6 seconds. 👍

Most likely these pages wouldn't change very often and would be cached so it's not an emergency or anything.

The name of the static method 'new' in the Atlas class seems to generate a parse error in third party libraries like phpDocumentor

@pmjones --

I've just had an issue while trying to parse annotations with phpDocumentor within a class that was using the 'new' static method of the Atlas class. The static function 'new' of the main Atlas class seems to be interpreted as the T_NEW PHP symbol and, therefore, generates a parse error. Is there any specific reason why this name was chosen? If not, I would suggest the following change to the main Atlas class:

[...]
    public static function new(...$args) : Atlas
    {
        return static::create(...$args);
    }
    public static function create(...$args) : Atlas
    {
        $transactionClass = AutoCommit::CLASS;
        $end = end($args);
        if (is_string($end) && is_subclass_of($end, Transaction::CLASS)) {
            $transactionClass = array_pop($args);
        }
        $connectionLocator = ConnectionLocator::new(...$args);
        $tableLocator = new TableLocator(
            $connectionLocator,
            new MapperQueryFactory()
        );
        return new Atlas(
            new MapperLocator($tableLocator),
            new $transactionClass($connectionLocator)
        );
    }
[...]

If this makes sense, please let me know and I can send a PR to you.

Many thanks!

Best regards,

Andrew Caya

Question : Validation

Hi Paul,

I noticed in your todo :

- sanitize/validate Records and Rows -- as events

Probably I may be missing something. But I feel the current events don't check if they return true or false and always execute the remaining stuff. So I believe even if the validation fails it will insert / update / delete things. So the question is do we really need a good event handler like Aura.Signal here ?

The idea is to stop on failures completely.

suggestion: variable naming and meaning suggestion

@pmjones @harikt

Just a suggestion / idea about variable naming
Assuming we have users table with fields -- id, name, status

$colsVals = [ 'name' => 'Joe Doe', 'status' => 'active' ]
--- assoc array
-- at application level, we normally use $post, $data, $config to imply assoc data array

$cols = [ name, status ]
--- numeric array

$vals = [ 'joe doe', 'active' ]
--- numeric array

$colsMeta / $colsSchema / $colsDetails = [ ... ]
--- array of arrays
--- the array result produced by SHOW COLUMNS FROM users-table

Extra Notes:
--- $cols is sometimes used to represent $colsVals + $colsMeta
--- $colsVals are mostly consistent already

Cached Queries

any plans or ideas of how to implement cached results, records or recordsets?

Question : Turn on Profiler

Hi Paul,

Thank you for your good work.

I was wondering is there a way to turn on the profiler of ExtendedPdo . From the AtlasContainer it looks the ExtendedPdo is instantiated there.

Is there a way to view the query executed some how other than via the ExtendedPdo profiler ?

Thank you.

Opinion: Insert() is better word than Create()

@pmjones @harikt

create() has 2 possible meanings:
-- instantiate a new object
-- add a record into the database table / mongo collection

insert() has 1 possible meaning:
-- add a record into the database table / mongo collection
( it doesn't have alternative concept interference )

Any thoughts ?

Wrong parameters for PDOException?

Was trying to get the following:

PDOException (23000) SQLSTATE[23000]: Integrity constraint violation...

But I eneded up with:

Wrong parameters for PDOException([string $message [, long $code [, Throwable $previous = NULL]]])

because:

throw new $c($e->getMessage(), $e->getCode(), $e);

I think casting the code to an int resolves the issue, but I havent inspected enough to know if thats the correct solution:

throw new $c($e->getMessage(), (int) $e->getCode(), $e); 

Creating new record with related

Consider the following:
I have a post mapper with a many to one relationship for the author.

$author = $atlas->fetchRecord(
    AuthorMapper::class,
    'juser'
);

$post = $atlas->newRecord(
    PostMapper::class,
    [
        'title' => 'New Post Title',
        'post'=>'Lorem ipsum dolar sit amet',
    ]
);
// Set the author related to an author record.
$post->author = $author;

// This fails as it doesn't set the foreign key to the primary key of the author record.
$success = $atlas->insert($post);

// However, this works fine
$post->author_id = $author->id;
$success = $atlas->insert($post);

Should scenario 1 work? IE, setting a related to the associated object directly.

The same holds true for updating a post record. IE setting the related author to a different author record.

Thanks.

Persist and removing relateds

Me again :)

So, I am wondering what is the appropriate method for removing related records with respect to the persist method.

Example 1

// This does not remove taggings, but should it?
$post = $this->atlas->fetchRecord(PostsMapper::class, $id, ['taggings']);
$removed = $post->taggings->removeAllBy(['post_id'=>$id]);
$success = $this->atlas->persist($post);

Example 2

// I sort of thought this might work
$post = $this->atlas->fetchRecord(PostsMapper::class, $id, ['taggings']);
// set to an empty record set
$post->taggings = $this->atlas->newRecordSet(TaggingsMapper::class);
$success = $this->atlas->persist($post);

Example 3

// This obviously works...
$post = $this->atlas->fetchRecord(PostsMapper::class, $id, ['taggings']);
$removed = $post->taggings->removeAllBy(['post_id'=>$id]);
foreach ($removed as $tagging) {
    $this->atlas->delete($tagging);
}
$success = $this->atlas->persist($post);

So, should example 1 or 2 work, or is number 3 the best method?

Managing long running transactions

Hello,

This is more a enhancement issue than a bug. Current Transactions objects can be executed and will perform a list of write operations. But I think that this is a very short-see of what a transaction is.

The main problem is that as soon as you started a transaction, the concurrency model of the databases starts to play, and ensure the Isolation principle (I from ACID). This is the thing which may lead to locks and deadlocks problems. But the main point here is that to perform valid tasks in a transactional application all reads used in your transaction must be done inside the transaction. If some of the data used in updates comes from the database, the only safe way to ensure this data is valid is to read that data after the BEGIN, not before. That's the way it works. without that all your reads are not managed by the isolation mechanism of the database (which may wait for locks set by your own transaction or generate hard-to-debug integrity errors).

So most ORM will give you more control on transaction objects, with begin(), rollback() and commit() methods, not just a simple execute(). This can help you write code where the transaction control is set on application level and not on the DAO level, because the transaction control is not a simple stack of write operations. At least not always and not on all applications. Think for examples that some write operations may trigger some triggers, and you may need to get back some computed data, manipulate it, and do some more inserts.

I do not like the exec() method, because it prevent the knowledge spread of real transactions stuff from developers, but I understand why it could exists. Even so, I think the transaction object should implement several other methods to be usable in the application level :

  • begin()
  • abort()
  • commit()
  • setIsolationLevel()

The second problem is $mapper->getWriteConnection(), being able to use different connections for write and read operations is a really nice feature, but the user should be able to use the write connection for reads, when theses reads are made inside a transaction. Maybe something like detecting a connection is currently running (static method on a transaction Singleton?) and switching all connections mappers request to the write mapper until the connection ends.

$atlas->select()->cols()->fetchRecord() throws exception when you don't include the primary key col

The following will throw an exception because cols does not include post_id.

// Only want the title col
$post = $atlas->select(PostsMapper::class)
    ->where('post_id = ?', 3)
    ->cols(['title', 'summary'])
    ->fetchRecord();

You should be able to fetch rows without including the primary key. Is there another way to calculate the identity key for the map?

Stack:

Atlas\Orm\Exception: Expected scalar value for primary key &#039;post_id&#039;, got array instead. in /home/jelofson/www/atlas/vendor/atlas/orm/src/Exception.php:243
Stack trace:
#0 /home/jelofson/www/atlas/vendor/atlas/orm/src/Table/AbstractTable.php(700): Atlas\Orm\Exception::primaryValueNotScalar(&#039;post_id&#039;, Array)
#1 /home/jelofson/www/atlas/vendor/atlas/orm/src/Table/AbstractTable.php(560): Atlas\Orm\Table\AbstractTable-&gt;calcIdentity(Array)
#2 /home/jelofson/www/atlas/vendor/atlas/orm/src/Table/TableSelect.php(309): Atlas\Orm\Table\AbstractTable-&gt;getSelectedRow(Array)
#3 [internal function]: Atlas\Orm\Table\TableSelect-&gt;fetchRow()
#4 /home/jelofson/www/atlas/vendor/atlas/orm/src/Mapper/MapperSelect.php(98): call_user_func_array(Array, Array)
#5 /home/jelofson/www/atlas/vendor/atlas/orm/src/Mapper/MapperSelect.php(217): Atlas\Orm\Mapper\MapperSelect-&gt;__call(&#039;fetchRow&#039;, Array)
#6 /home/jelofson/www/atlas/src/Blog/Controllers/Posts.php(28): Atlas\Orm\Mapper\MapperSelect-&gt;fetchRecord()
...

Pagination

Hi,

When using SqlMapper_Bundle one thing I think is we missed is explicit pagination support. Will atlas provide a way to make use of pagination with the SqlQuery library easily.

So something like

$postRecordSet = $atlas
    ->select(PostMapper::CLASS)
    ->orderBy('post_id DESC')
    ->limit(10)
    ->with([
        'author',
    ])
    ->page(1)
    ->fetchRecordSet();

Thank you

Cannot delete all records in a many-to-many

Hi all,

I'm trying to delete all relations in a many-to-many relationship and it's not working for me - no DELETE sql statement is executed.

I have a many-to-many relationship between A and C via B. On A, if I set a new non-empty RecordSet on A->C then it works fine and records in B are deleted and created as expected.

However, if I set an empty RecordSet on A->C, or if I call A->C->detachAll() or if I set A->C = null then no DELETE sql is issued.

I've also tried the same actions on the A->B RecordSet with the same result.

Is this a bug or am I doing something wrong?

In all cases I'm calling A->persist().

Require PHP 7 ?

(Open discussion topic.)

I am preparing another alpha release of Atlas. Currently it requires PHP 5.6, and this upcoming alpha will continue with that requirement.

However, for the next release after that (whether alpha or beta), I think upgrading the requirement to PHP 7 will provide some advantages. Typehinting parameters and returns alone will be great.

Does anyone using Atlas have arguments against this?

Best way to handle sorting?

I'm working on a site that has a very complex interrelated database structure.

https://github.com/andrewshell/pen-paper-2/tree/0.1.0

Here is the most complex query in my application:
CreatorsAtlasRepository::getCreatorById

Here is an example of one of the relationships.

Creator─RpgBookCreator─┬─RpgBook─┬─Publisher
                       │         │
                       │         └─GameLine
                       │
                       └─Credit

Ideally, I'd sort the RpgBookCreators by RpgBook.title but from what I can tell, there is no easy way of doing that through the API.

My solution was to getArrayCopy and then run the result through a series of usorts.

I'm wondering if there is a better way to handle these sorts or if what I'm doing is the best practice at this time.

joinWith doesn't work when specifying direction

Hi

When doing a joinWith('LEFT JOIN relationship') it will crash with Undefined index: JOIN relationship

The problem is in MapperRelationships::joinSelect on line 297 when removing the join part it don't include the length of JOIN

I'll try to put together a pull request later today.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.