atlasphp / atlas.orm Goto Github PK
View Code? Open in Web Editor NEWA data mapper implementation for your persistence model in PHP.
Home Page: http://atlasphp.io
License: MIT License
A data mapper implementation for your persistence model in PHP.
Home Page: http://atlasphp.io
License: MIT License
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"
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.
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:
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);
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 'post_id', 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('post_id', Array)
#1 /home/jelofson/www/atlas/vendor/atlas/orm/src/Table/AbstractTable.php(560): Atlas\Orm\Table\AbstractTable->calcIdentity(Array)
#2 /home/jelofson/www/atlas/vendor/atlas/orm/src/Table/TableSelect.php(309): Atlas\Orm\Table\AbstractTable->getSelectedRow(Array)
#3 [internal function]: Atlas\Orm\Table\TableSelect->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->__call('fetchRow', Array)
#6 /home/jelofson/www/atlas/src/Blog/Controllers/Posts.php(28): Atlas\Orm\Mapper\MapperSelect->fetchRecord()
...
Hi Paul,
in apps, there are normally 2 types of updates:
I think the patch probably should have some MapperEvents methods, but no need for TableEvents methods, any thought ?
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.
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.
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?
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'
]
);
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:
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.
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?
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.
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.
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.
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 ?
@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
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?
At https://github.com/atlasphp/Atlas.Orm/blob/1.x/docs/getting-started.md#instantiating-atlas add a note about passing an ExtendedPdo connection, or an entire ConnectionLocator, for instantiation.
Hello, good afternoon.
I have a problem using the with method when getting relationships with the fetchRecordSet.
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:
How we can solve this 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.
Add a graphical database model to illustrate the tables, columns and relations. (Optimal would we be the SVG format.)
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.
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
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
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.
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.
// 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
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.
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?
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;
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:
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
}
}
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?
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.
id | user_id | title | body |
---|---|---|---|
1 | 1 | something | something |
2 | 1 | another | another |
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
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
Probably on the "Direct Queries" page.
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?
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.
(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?
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
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.
Atlas.Orm/src/Table/AbstractTable.php
Lines 363 to 366 in bf4d2de
Changing the getAutoinc
method in the specific table class doesn't work, because the method AbstractTable::insertRowPrepare()
needs the name of the autoincrement column.
Atlas.Orm/src/Table/AbstractTable.php
Lines 329 to 332 in bf4d2de
In my project I get a exception "Row does not exist in IdentityMap.". It would be nice to have the row name in this exception text like "Row "blablub" does not exist in IdentityMap "fooBar"".
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.
//
// first create a select query
//
$query2 = clone $query;
$query2->cols(['test_field']);
dump($query->getStatement()); // ---- this will show test_field in the statement
This might be handy
any plans or ideas of how to implement cached results, records or recordsets?
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.
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().
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.