承接 vox/restfull-client-mapper 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

vox/restfull-client-mapper

Composer 安装命令:

composer require vox/restfull-client-mapper

包简介

README 文档

README

Scrutinizer Code Quality Code Coverage Build Status Build Status

Rest Mapper

A Restfull Resource Relational Mapper

1. Instalation

composer require vox/restfull-client-mapper

2. Creating a Transfer Manager

// 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. Using the TransferManagerBuilder

The builder class enables a more configuration style creation of the transfer manager.

The most basic setup would be the following, to obtain a manager with annotations driver, with no caching system:

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();

to add caching system simply change it on the builder

$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

to create with yml driver:

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

3.2 Using transactional mode

Sometimes, you need things to be rolled back when something goes wrong just like you can using a database. That behavior is possible with RRM too. Using the builder just call the method isTransactional.

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

Now, when something goes wrong, the manager will try to restore all data by doing posts, updates and deletions to the previows values on your webservice

3. Mapping a Transfer

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. the yml driver

if you want to leave the mapping metadata out of your object to keep it clean or keep it decoupled from this lib you can also use the yml metadata reader

// 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 mapping

the yml mapping must be named after the complete class namespace replacing backslashes by dots. Ex: /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. Composite Ids

Composite id's are supported, however there are some limitations.

To map composite ids using annotations is really simple

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

The yaml mapping is also trivial

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

id: [firstId, secondId]

3.2.1 Mapping relationships with composite ids transfers

For now it is only possible to map belongs to relationships when linking to a composite ids transfer.

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]

There are some limitations however, never use setters for ids or else it will not be able to post new transfers. new transfers needs to pull the ids from the foreign keys automaticaly by the unity of work

4. Using the Transfer Manager

// 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 relationships support

if your webservice uses iri relationships, you can map it using the following

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

or with Yaml

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

parameters:
    relatedTransfers:
        hasMany:
            iriCollectionField: related

the iriCollectionField is the field that conatiains an array of iri addresses, a collection of objects will be created as soon as you call the relatedTransfers, but have in mind, it will call the webservice one by one.

The other situations like belongs to, will be taken care automaticaly, just map as commom relationships, the lib detects if its an iri address using a regexp that looks for the format ".+/.+$"

6. Doctrine Interop

This lib uses the doctrine commom interfaces, so you can do code wich is interoperable with doctrine. The annotation mapping however is completely different, hence the use of the yaml mapping is encouraged (also using yml or xml mapping for doctrine projects is also encouraged, in order to create a domain decoupled from the framework)

7. The event system

The event system is made to be compatible with doctrine's, so you can have easy time importing plain objects from other system and have its events on your consumer

Create a listener class

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) {}
}

Register the listener

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);

Limitations

  • The unity of work implementation of this lib uses the ids of the entities to determine the state of the entity, so using setters for the id fields may result in the entity being considered as untouched even if its a new entity wich should be posted. To avoid problems, prefer to let the lib manage the id fields automaticaly.
  • Theres no many to many relationships handling, therefore if needed the associative table of the many to many association has to be mapped on the api and managed manualy, setting the transfers on it, do not set the ids manualy, manage only the objects around

vox/restfull-client-mapper 适用场景与选型建议

vox/restfull-client-mapper 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 11 次下载、GitHub Stars 达 0, 最近一次更新时间为 2017 年 12 月 14 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 vox/restfull-client-mapper 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 vox/restfull-client-mapper 我们能提供哪些服务?
定制开发 / 二次开发

基于 vox/restfull-client-mapper 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 11
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 9
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2017-12-14