auraphp / aura.marshal Goto Github PK
View Code? Open in Web Editor NEWA data-marshaling toolset
License: MIT License
A data-marshaling toolset
License: MIT License
In longer running processes or entity-heavy processes it may occur that you reach the memory_limit of PHP. Increasing it may not be an option, but I'd like to see some kind of clearance for the loaded entities to free some space (e.g., after a round of batch processing several thousand entities, remove everything that was loaded so far). As far as I could guess I think Marshal works similar to the Doctrine2 UnitOfWork, keeping references to generated object instances and reusing them. Doctrine2 has the same problem if you don't clear the UnitOfWork on a regular basis.
See the Doctrine2 UnitOfWork to get some sort of an idea:
I think this feature should be proposed for the 2.0 version of Marshal?
The examples are database-centric, it would be nice to have an example or documentation related to using this in an API context.
This does appear to be a use case that exists. Searching Packagist for terms such as api relationship
or data relationship
returns a small handful of libraries.
Most of these are unsuitable for some reason, such as lack of maintenance, poor documentation, or tight binding to a particular ORM (such as Doctrine) or framework (such as Laravel). However these two stand out as possibilities:
Neither of these are as elegant or well documented as Aura.Marshal.
As an aside, Aura.Marshal is the top hit for the search term data relationship
. It would be nice for this to show up in the api relationship
search results as well.
This probably works fine in later versions of PHP, but it would be helpful if this were proven and noted.
I experienced some trouble when implementing my own entity because of the GenericLazy.
I figured out that \Aura\Marshal\Entity\MagicArrayAccessTrait make the "transformation" of the GenericLazy to the real class. But MagicArrayAccessTrait only works with an implementation of \ArrayAccess, that is made with \Aura\Marshal\Data.
So, I had to extends \Aura\Marshal\Data and use \Aura\Marshal\Entity\MagicArrayAccessTrait to make my entity works properly.
Should be good if MagicArrayAccessTrait makes the whole thing alone.
And it documented on README.md
Aura.Marshal git:develop ❯ phpunit tests
PHP Fatal error: Trait 'Aura\Framework\Test\WiringAssertionsTrait' not found in /Users/sobstel/Projects/Aura.Marshal/tests/WiringTest.php on line 8
Fatal error: Trait 'Aura\Framework\Test\WiringAssertionsTrait' not found in /Users/sobstel/Projects/Aura.Marshal/tests/WiringTest.php on line 8
I have 3 posts , with ids 1, 2, 3 . If there is no summary for 3rd post
post_id read_sum
1 4
2 2
trying to get $post->summary->read_sum
will throw exception. I feel it should not throw the exception. Playing on develop branch with the same example in README.md .
Beta1 : 2022 of August first week.
4.x : 2022 of August if all is well.
Lets say you have loaded data with relations and now wants to serialize / json your data
// get a collection of the post IDs we just loaded
$posts = $manager->posts->getCollection($post_ids);
print_r($posts[0]); //will throw me huge pile of code that kills my browser.
Sure I could build an array by accessing individual properties like $posts[0]->name... And so on,but that's not convenient.
This package uses the old PSR-0 autoloader. It would be nice to upgrade that to the more common PSR-4.
I read this in the example of the "Loading Data" section :
// add the authors for the posts. first, we need to know
// the author_id values for all the posts so far ...
$author_ids = $manager->posts->getFieldValues('author_id');
// ... then we can query and load.
$result = $db->fetchAll(
'SELECT * FROM authors WHERE id IN (?)',
[$author_ids]
);
$manager->comments->load($result);
I think the last line should be '$manager->authors->load($result);' instead of '$manager->comments->load($result);'.
Am I right ?
The Aura\Marshal\DataIterator constructor expects an instance of Aura\Marshal\Data, but the only method it calls on the Data object is offsetGet(), which is part of the ArrayAccess interface. Is there any reason why the DataIterator constructor can't be modified to expect ArrayIterator rather than Data?
Loading "aura/installer-default" which is a legacy composer-installer built for Composer 1.x, it is likely to cause issues as you are running Composer 2.x.
Currently https://github.com/auraphp/Aura.Marshal/blob/1.x/src/Aura/Marshal/Entity/Builder.php#L49 forces you to have public properties or using one of the provided traits which define __set
magic method.
To allow having entities without this magic, I'd recommend to improve the Entity\Builder a bit to write into private/protected properties directly using a class bound setter function.
So instead of doing
foreach ($data as $field => $value) {
$entity->$field = $value;
}
we could do
$writer = \Closure::bind(
function ($object, $prop, $value) {
$object->$prop = $value;
},
null,
get_class($entity)
);
foreach ($data as $field => $value) {
$writer($entity, $field, $value);
}
@pminnieur
Some use cases require using plain PHP arrays, but getCollection() returns an object.
A naive conversion fails:
$array = iterator_to_array(
$manager->getCollection(...)->getIterator()
);
This fails because although $array is an array, its elements are objects of class Aura\Marshal\Entity\GenericEntity
.
If this can be done with a custom builder, then a documentation example for that should be added.
I was in an experiment to look how we can incorporate Marshal inside the SqlMapper bundle. Have noticed a few issues, may be I am wrong.
In advanced usage, we have Entity and Collection Builders . The only dependency was to make use of the BuilderInterface.
The example is taken from the same readme. Running it gives
PHP Notice: Trying to get property of non-object in /var/www/github.com/harikt/marshal/marshal.php on line 169
PHP Notice: Trying to get property of non-object in /var/www/github.com/harikt/marshal/marshal.php on line 170
PHP Notice: Trying to get property of non-object in /var/www/github.com/harikt/marshal/marshal.php on line 170
PHP Notice: Trying to get property of non-object in /var/www/github.com/harikt/marshal/marshal.php on line 171
The post titled was written by . and has 0 comments. PHP Notice: Trying to get property of non-object in /var/www/github.com/harikt/marshal/marshal.php on line 174
PHP Fatal error: Call to a member function isEmpty() on a non-object in /var/www/github.com/harikt/marshal/marshal.php on line 174
Full example is below .
<?php
namespace Vendor\Package\Entity;
use Aura\Marshal\Entity\MagicPropertyTrait;
class PostCollection
{
public $data;
public function __construct($data)
{
$this->data = $data;
}
public function isEmpty()
{
return empty($this->data);
}
}
<?php
namespace Vendor\Package\Entity;
use Aura\Marshal\Entity\MagicPropertyTrait;
class Post
{
protected $id;
protected $title;
protected $author;
protected $comments;
protected $tags;
use MagicPropertyTrait;
public function __construct($data)
{
foreach ($data as $key => $value) {
$this->{$key} = $value;
}
}
}
<?php
namespace Vendor\Package\Builder\Post;
use Aura\Marshal\Entity\BuilderInterface;
use Vendor\Package\Entity\Post;
class EntityBuilder implements BuilderInterface
{
public function newInstance(array $data)
{
return new Post($data);
}
}
<?php
namespace Vendor\Package\Builder\Post;
use Aura\Marshal\Collection\BuilderInterface;
use Vendor\Package\Entity\Post;
use Vendor\Package\Entity\PostCollection;
class CollectionBuilder implements BuilderInterface
{
public function newInstance(array $data)
{
$instances = array();
foreach ($data as $row) {
$instances[] = new Post($row);
}
$collection = new PostCollection($instances);
return $collection;
}
}
<?php
require __DIR__ . '/vendor/autoload.php';
use Aura\Marshal\Manager;
use Aura\Marshal\Type\Builder as TypeBuilder;
use Aura\Marshal\Relation\Builder as RelationBuilder;
use Aura\Sql\ExtendedPdo;
$manager = new Manager(new TypeBuilder, new RelationBuilder, [
'authors' => [
'identity_field' => 'id',
'relation_names' => [
'posts' => [
'relationship' => 'has_many',
'native_field' => 'id',
'foreign_field' => 'author_id',
],
],
],
'posts' => [
'identity_field' => 'id',
'index_fields' => ['author_id'],
'entity_builder' => new \Vendor\Package\Builder\Post\EntityBuilder,
'collection_builder' => new \Vendor\Package\Builder\Post\CollectionBuilder,
'relation_names' => [
'comments' => [
'relationship' => 'has_many',
'native_field' => 'id',
'foreign_field' => 'post_id'
],
'author' => [
'relationship' => 'belongs_to',
'foreign_type' => 'authors',
'native_field' => 'author_id',
'foreign_field' => 'id',
],
'tags' => [
'relationship' => 'has_many_through',
'through_type' => 'posts_tags',
'native_field' => 'id',
'through_native_field' => 'post_id',
'through_foreign_field' => 'tag_id',
'foreign_field' => 'id'
],
],
],
'comments' => [
'identity_field' => 'id',
'index_fields' => ['post_id'],
'relation_names' => [
'post' => [
'relationship' => 'belongs_to',
'foreign_type' => 'posts',
'native_field' => 'post_id',
'foreign_field' => 'id',
],
],
],
'posts_tags' => [
'identity_field' => 'id',
'index_fields' => ['post_id', 'tag_id'],
'relation_names' => [
'post' => [
'relationship' => 'belongs_to',
'foreign_type' => 'posts',
'native_field' => 'post_id',
'foreign_field' => 'id',
],
'tag' => [
'relationship' => 'belongs_to',
'foreign_type' => 'tags',
'native_field' => 'tag_id',
'foreign_field' => 'id',
],
],
],
'tags' => [
'identity_field' => 'id',
'relation_names' => [
'posts' => [
'relationship' => 'has_many_through',
'native_field' => 'id',
'through_type' => 'posts_tags',
'through_native_field' => 'tag_id',
'through_foreign_field' => 'post_id',
'foreign_field' => 'id'
],
],
],
]);
/*
$manager->setType('posts', [
// the field with the unique identifying value
'identity_field' => 'id',
// an object to build entities; default is a new instance of
// Aura\Marshal\Entity\Builder
'entity_builder' => new \Vendor\Package\Builder\Post\EntityBuilder,
// an object to build collections; default is a new instance of
// Aura\Marshal\Collection\Builder
'collection_builder' => new \Vendor\Package\Builder\Post\CollectionBuilder,
]);
*/
$pdo = new ExtendedPdo(
'mysql:host=localhost;dbname=marshal',
'root',
'mysqlroot',
array(), // driver options as key-value pairs
array() // attributes as key-value pairs
);
$result = $pdo->fetchAll('SELECT * FROM posts LIMIT 10');
// load the results into the posts type object, and get back the
// identity (primary key) values for the loaded results.
$post_ids = $manager->posts->load($result);
// select and load all the comments on all the posts at once.
$result = $pdo->fetchAll(
'SELECT * FROM comments WHERE post_id IN (:post_ids)',
[
'post_ids' => $post_ids,
]
);
$manager->comments->load($result);
// add the authors for the posts. first, we need to know
// the author_id values for all the posts so far ...
$author_ids = $manager->posts->getFieldValues('author_id');
// ... then we can query and load.
$result = $pdo->fetchAll(
'SELECT * FROM authors WHERE id IN (:author_ids)',
[
'author_ids' => $author_ids,
]
);
$manager->authors->load($result);
// query and load the association mapping type linking posts and tags
$result = $pdo->fetchAll(
'SELECT * FROM posts_tags WHERE post_id IN (:post_ids)',
[
'post_ids' => $post_ids,
]
);
$manager->posts_tags->load($result);
$tag_ids = $manager->posts_tags->getFieldValues('tag_id');
// finally, query and load all tags regardless of posts
$result = $pdo->fetchAll('SELECT * FROM tags WHERE id IN (:tag_ids)',
[
'tag_ids' => $tag_ids,
]
);
$manager->tags->load($result);
// get a collection of the post IDs we just loaded
$posts = $manager->posts->getCollection($post_ids);
// loop through posts collection, getting a post entity each time
foreach ($posts as $post) {
// address the native and foreign fields
echo "The post titled {$post->title} "
. "was written by {$post->author->name}. "
. "and has " . count($post->comments) . " comments. ";
// loop through the tags
if ($post->tags->isEmpty()) {
echo "It has no tags.";
} else {
echo "It has these tags: ";
$tags = [];
foreach ($post->tags as $tag) {
$tags[] = $tag->name;
}
echo implode(', ', $tags);
}
echo PHP_EOL;
}
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.