vox/restfull-client-mapper

0.6.0 2018-05-18 20:09 UTC

This package is auto-updated.

Last update: 2024-09-16 14:22:30 UTC


README

Scrutinizer Code Quality Code Coverage Build Status Build Status

Rest Mapper

RESTful资源关系映射器

1. 安装

composer require vox/restfull-client-mapper

2. 创建传输管理器

// uses guzzle to reach webservices
$guzzleClient = new GuzzleHttp\Client();

// obtain a client registry, and register all guzzle clients on it
$registry = new Vox\Webservice\ClientRegistry();
$registry->set('some_client', $guzzleClient);

// instantiate a metadata factory, the second argument for the annotation driver
// is a string with the metadata classes that will be created by the driver
$metadataFactory = new Metadata\MetadataFactory(
    new Vox\Metadata\Driver\AnnotationDriver(
        new Doctrine\Common\Annotations\AnnotationReader(),
        Vox\Webservice\Metadata\TransferMetadata::class
    )
);

// A symfony serializer using the provided normalizers on this lib
// is important, however, other normalizers/denormalizers can be used
$serializer = new Symfony\Component\Serializer\Serializer(
	[
    	new Vox\Serializer\Normalizer($metadataFactory),
        new Vox\Serializer\Denormalizer(new ObjectHydrator($metadataFactory))
    ], 
    [
    	new Symfony\Component\Serializer\Encoder\JsonEncoder()
    ]
);
// create a webservice client, its the guy who actualy calls your webservices
$webserviceClient = Vox\Webservice\WebserviceClient($registry, $metadataFactory, $serializer, $serializer);

// Finaly you can obtain a transfer manager
$transferManager = new Vox\Webservice\TransferManager($metadataFactory, $webserviceClient)

3.1. 使用TransferManagerBuilder

构建器类允许以更配置化的方式创建传输管理器。

最基本的设置如下,获取一个带有注解驱动的管理器,没有缓存系统

use Vox\Webservice\ClientRegistry;
use Vox\Webservice\Factory\ClientFactory;
use Vox\Webservice\Factory\TransferManagerBuilder;

$clientRegistry = new ClientRegistry();
$clientFactory = (new ClientFactory())
    ->addClient('foo', 'http://foo.localhost', $clientRegistry)
    ->addClient('bar', 'http://bar.localhost', $clientRegistry);
    
$builder = new TransferManagerBuilder();
$builder->withClientRegistry($clientRegistry)
    ->withClientFactory($clientFactory);
    
$transferManager = $builder->createTransferManager();

要添加缓存系统,只需在构建器上更改即可

$builder->withMetadataCache('file')
    ->withCacheDir('/tmp/cache');
    
// Or if you want doctrine caches
$builder->withDoctrineCache(new ApcCache());

//Note that the method withCacheDir, also sets the proxy cache folder

要使用yml驱动器创建

$builder->withMetadataDriver('yaml')
    ->withMetadataPath('src/Resources/metadata');

3.2 使用事务模式

有时,当出现错误时,你需要像使用数据库一样回滚事物。这种行为在RRM中也是可能的。使用构建器只需调用isTransactional方法。

$builder->isTransactional();
// you can also disable by calling $builder->isTransactional(false);

现在,当出现错误时,管理器将尝试通过向您的webservice上的先前值执行POST、更新和删除操作来恢复所有数据

3. 映射传输

use Vox\Webservice\Mapping\BelongsTo;
use Vox\Webservice\Mapping\Id;
use Vox\Webservice\Mapping\Resource;

/**
 * Remember the name setted on the client registry? it will be resolved to the name used on the
 * client property on the resource annotation. The route of the resource can be configured on the
 * route property of the annotation
 *
 * @Resource(client="some_client", route="/related")
 */
class Stub
{
    /**
     * Maps an property as an id, this is mandatory to update and find by id
     *
     * @Id
     *
     * @var int
     */
    private $id;
    
    /**
     * bind this property to receive the id value of a foreign key
     *
     * @Bindings(source="relation")
     * 
     * @var int
     */
    private $relationId;
    
    /**
     * does the belongs to relationship mapping, a existing field containing the id of the relation must be indicated
     *
     * @BelongsTo(foreignField = "relationId")
     * 
     * @var RelationStub
     */
    private $belongs;
    
    /**
     * does the has one relationship mapping, must indicate a field on the related class that will be matched against
     * the value contained on the id of this class
     *
     * @HasOne(foreignField = "relatedId")
     * 
     * @var RelationStub
     */
    private $hasOne;

    /**
     * does the has many relationship mapping, must indicate a field on the related classes that will be matched against
     * the value contained on the id of this class
     *
     * @HasMany(foreignField = "relatedId")
     * 
     * @var RelationStub
     */
    private $hasMany;
    
    public function getRelationId()
    {
        return $this->relationId;
    }

    public function getBelongs(): RelationStub
    {
        return $this->belongs;
    }
    
    public function setBlongs(RelationStub $relation)
    {
        $this->belongs = $relation;
    }

    public function getHasOne(): RelationStub
    {
        return $this->hasOne;
    }
    
    public function getHasMany(): TransferCollection
    {
        return $this->hasMany;
    }
}

/**
 * @Resource(client="some_client", route="/relation")
 */
class RelationStub
{
    /**
     * @Id
     *
     * @var int
     */
    private $id;
    
    private $name;
    
    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }
    
    public function setName($name)
    {
        $this->name = $name;
    }
}

3.1. yml驱动器

如果您想从对象中排除映射元数据以保持其清洁或将其与该库解耦,您还可以使用yml元数据读取器

// the first argument is the path of where the yml files will be located, the second one is the metadata class to be used
$metadataFactory = new Metadata\MetadataFactory(
    new Vox\Metadata\Driver\YmlDriver(
        '/project/metadata',
        Vox\Webservice\Metadata\TransferMetadata::class
    )
);

3.1.1 - Yml映射

yml映射必须以完整的类命名空间命名,将反斜杠替换为点。例如:/propject/metadata/Project.Package.ClassName.yml

resource: 
    client: some_client
    route: http://lorem-dolor.cc/some/route

id: id

parameters:
    id:
        bindings:
            source: id
    authorId:
        bindings:
            source: author_id
    date:
        bindings:
            source: post_date
    author:
        belongsTo: 
            foreignField: authorId
    details:
        hasOne:
            foreignField: blogPostId
    comments:
        hasMany:
            foreignField: blogPostId

3.2. 复合ID

复合ID受支持,但有一些限制。

要使用注解映射复合ID非常简单

class Foo
{
    /**
     * @Id()
     *
     * @var int
     */
    private $firstId;
    
    /**
     * @Id()
     *
     * @var int
     */
    private $secondId;
}

yaml映射也是简单的

resource: 
    client: some_client
    route: http://lorem-dolor.cc/some/route

id: [firstId, secondId]

3.2.1 使用复合ID传输映射关系

目前,仅当链接到复合ID传输时才能映射属于关系。

class Bar
{
    /**
     * @var int
     */
    private $foreignKeyOne;
    
    /**
     * @var int
     */
    private $foreignKeyTwo;
    
   /**
    * All you need is to use an array of foreign keys instead of a single one
    *
    * @BelongsTo(foreignField={"foreignKeyOne", "foreignKeyTwo"})
    *
    * @var Relationship
    */
    private $relationship;
}
resource: 
    client: some_client
    route: http://lorem-dolor.cc/some/route

id: id

parameters:
    relationship:
        belongsTo: 
            foreignField: [foreignKeyOne, foreignKeyTwo]

但是有一些限制,不要使用ID的setter,否则将无法发布新的传输。新的传输需要自动从外键中提取ID,这是由工作单元实现的

4. 使用传输管理器

// fetches a single transfer from the webservice
$stub = $transferManager->find(Stub::class, 1);
// thanks to the proxy pattern and the mapping the relation can be retrieved lazily and automaticly
$relation = $stub->getRelation();

// changes to a proxyed transfer will be tracked
$relation->setName('lorem ipsum');

$stub2 = $transferManager->find(Stub::class, 2);
$stub2->setRelation(new Relation());

$stub3 = new Stub();
$stub3->setRelation(new Relation());

// any new created transfer must be persisted into the unity of work, so it can be posted by the persister
$transferManager->persist($stub3);

// flushes all changes, all posts, puts, etc. will happen here
$transferManager->flush();

5. Iri关系支持

如果您的webservice使用iri关系,您可以使用以下方式映射它

/**
 * @Resource(client="foo", route="/foo")
 */
class IriRelated
{
    /**
     * @Id
     *
     * @var int
     */
    private $id;
    
    /**
     * @var array
     */
    private $related;
    
    /**
     * @HasMany(iriCollectionField="related")
     * 
     * @var IriRelated[]
     */
    private $relatedTransfers;
}

或使用Yaml

resource: 
    client: foo
    route: /foo
id: id

parameters:
    relatedTransfers:
        hasMany:
            iriCollectionField: related

iriCollectionField是包含iri地址数组的字段,当您调用relatedTransfers时,将创建一个对象集合,但请记住,它将逐个调用webservice。

其他情况,如属于,将自动处理,只需像普通关系一样映射,库将检测它是否是iri地址,使用一个正则表达式查找".+/.+$"格式

6. Doctrine互操作

此库使用doctrine公共接口,因此您可以编写与doctrine互操作代码。但是,注解映射完全不同,因此建议使用yaml映射(同样,使用yml或xml映射对于doctrine项目也是鼓励的,以便创建与框架解耦的域)

7. 事件系统

事件系统旨在与doctrine兼容,因此您可以轻松地将来自其他系统的普通对象导入并使消费者具有其事件

创建监听器类

class ExampleListener
{
    public function prePersist(LifecycleEventInterface $event) {}
    public function postPersist(LifecycleEventInterface $event) {}
    public function preUpdate(LifecycleEventInterface $event) {}
    public function preRemove(LifecycleEventInterface $event) {}
    public function postUpdate(LifecycleEventInterface $event) {}
    public function preFlush(LifecycleEventInterface $event) {}
    public function postFlush(LifecycleEventInterface $event) {}
}

注册监听器

use Vox\Webservice\Event\PersistenceEvents;
use Vox\Webservice\Factory\TransferManagerBuilder;
use Vox\Webservice\EventDispatcher;

//create the event dispatcher
$eventDispatcher = new EventDispatcher();

//register the listsner
$eventDispatcher
    ->addEventListener(
	[
	    PersistenceEvents::PRE_FLUSH, 
	    PersistenceEvents::PRE_PERSIST, 
	    PersistenceEvents::PRE_UPDATE, 
	    PersistenceEvents::PRE_REMOVE, 
	    PersistenceEvents::POST_FLUSH, 
	], 
	new ExampleListener()
    );
    
//register the event dispatcher
$managerBuilder = new TransferManagerBuilder();
$managerBuilder->withEventDispatcher($eventDispatcher);

限制

  • 本库的工作实现使用实体ID来决定实体的状态,因此对ID字段使用setter可能导致即使实体是应提交的新实体,实体仍被视为未更改。为了避免问题,建议让库自动管理ID字段。
  • 没有处理多对多关系,因此如果需要,多对多关联的关联表必须在API上映射并手动管理,设置其传输,不要手动设置ID,只管理周围的对象。