I have an interface with search function:
interface Searcher
{
public function search($text,$limit)
}
I have some realization based on API
class APISeach implements Searcher
{
public function search($text,$limit)
{
$params = [
'name' => $sName,
'maxRows' => $iLimit,
];
$response = Http::get('http://some_api_service/search', $params)->json();
}
}
And I have some code which used this Search:
class MyController extends Controller
{
private $seacher;
public function __construct(Searcher $mySeacher)
{
$this->seacher = $mySeacher;
}
public function search($text,$limit=10)
{
$this->seacher->search($text,$limit)
}
}
All looks fine (may be). But what if I need change API realization and it will be required another parameters. For example:
class AnotherAPISeach implements Searcher
{
public function search($text,$language,$fuzzy,$limit)
{
$ApiObjectFromLib = new ApiObjectFromLib();
$response = $ApiObjectFromLib->search($text,$language,$fuzzy,$limit)->json();
}
}
So it's can not implement Searcher interface any more.
Is it exists any way to use interfaces for API functions? All API can required various parameters and it's no good if I need change Interface or Controller code for each API.
You can use variadic arguments
interface Searcher
{
public function search($text, $limit, ...$additional);
}
If that defeats the purpose of the interface is up to you to decide 😉
​​​​​​​
How about something like this (demo)?
public function search(string $name, int $limit = \Search\Limit::DEFAULT)
{
return $this->api->search(
new \Search\Name($name),
new \Search\Limit($limit)
);
}
public function lookup(
string $name,
string $language = \Search\Language::DEFAULT,
bool $fuzzy = \Search\Fuzzy::DEFAULT,
int $limit = \Search\Limit::DEFAULT
) {
return $this->api->search(
new \Search\Name($name),
new \Search\Language($language),
new \Search\Fuzzy($fuzzy),
new \Search\Limit($limit)
);
}
These "specifications" look like this:
namespace Search
{
interface Specification
{
public function __invoke(array $params): array;
}
class Name implements Specification
{
private $name = null;
public function __construct(string $name)
{
$this->name = $name;
}
public function __invoke(array $params): array
{
return [
'name' => $this->name,
];
}
}
class Language implements Specification
{
const DEFAULT = 'en';
private $language = null;
public function __construct(string $language)
{
$this->language = $language;
}
public function __invoke(array $params): array
{
return [
'language' => $this->language ?? 'en',
];
}
}
class Fuzzy implements Specification
{
const DEFAULT = true;
private $fuzzy = null;
public function __construct(bool $fuzzy)
{
$this->fuzzy = $fuzzy;
}
public function __invoke(array $params): array
{
return [
'fuzzy' => $this->fuzzy,
];
}
}
class Limit implements Specification
{
const DEFAULT = 10;
private $max = null;
public function __construct(int $limit)
{
$this->limit = $limit;
}
public function __invoke(array $params): array
{
return [
'maxRows' => $this->limit ?: self::DEFAULT,
];
}
}
}
Which the searchable API composes like this:
interface Searchable
{
public function search(\Search\Specification... $criteria);
}
class Search implements Searchable
{
private $url = '/search';
private $defaults = [
'maxRows' => \Search\Limit::DEFAULT,
];
public function search(\Search\Specification ...$criteria)
{
return \Http::get($this->url, array_reduce(
$criteria,
fn($params, $criteria) => $criteria($params) + $params,
$this->defaults
))->json();
}
}
The Specification Pattern is interesting, since it implies that a request into a domain is really just a chain of decisions that result in a configuration that can be applied elsewhere.
For instance, note how above the $criteria($params) objects are each given the current $params for the request, for which it may override parameters, read and modify a parameter, or potentially incorporate a Specification check to validate parameters.
Note on the array + array syntax, which is a way to merge arrays:
['foo' => 'bar'] + ['foo' => 'baz'] // left takes precedence: ['foo' => 'bar']
Filter/Criteria is very similar; I tend to think of those having a tighter link to the object it's applied to (Repository, Query or Collection) than Specification, which in my mind applies more to what's to be gotten back.
Related
I'm making a Laravel package, which is a basic API Wrapper to practice. I want my code completely re-usable and neat, well that's the reason we learn OOP I think :P
Let me first attach my code, and I'll explain what I'm trying to achieve via comments.
// This is how I'm calling my class
Shiprocket::
withCredential('other-than-default') // this is optional
->order(203504661) // pass order id
->details() // finally fetch the details
// This is my main class it's behind a Larvel Facade Accessor
class Shiprocket
{
protected $credentials;
protected $token;
// I'm using it as a constructor to initilize with a different credentil pair.
public function withCredential($credential_id)
{
$this->credentials = config('shiprocket.credentials')[$credential_id];
$this->token = $this->getToken();
return $this;
}
public function __construct()
{
$this->credentials = config('shiprocket.credentials')[config('shiprocket.default_credentials')];
$this->token = $this->getToken();
}
public function order($order_id = null)
{
return new OrderResource($order_id);
// Here my doubt starts
// I want to return another class (OrderResource) for Order related methods
// so that we can call Order related methods like:
// Shiprocket::withCredential('my-credential')->order()->getAll()
// and those methods will also use methods & properties of this Main class
// like the token, get(), post()
}
public function shipment($shipment_id = null)
{
return new ShipmentResource($shipment_id);
// and maybe I can also have more child classes like OrderResource
// So that I can call similar methods as OrderResource for shipments like ... ->getAll()
// or ... ->status()
// but these methods won't be reusable - they'll be completely different, just sometimes
// might have same names.
}
public function getToken(): string
{
$duration = config('shiprocket.token_cache') ? config('shiprocket.token_cache_duration') : 0;
return cache()->remember("shiprocket-{$this->credentials['email']}", $duration, function () {
return Http::post("https://apiv2.shiprocket.in/v1/external/auth/login", [
'email' => $this->credentials['email'],
'password' => $this->credentials['password'],
])->json()['token'];
});
}
public function get($url, $data = null)
{
return Http::withToken($this->token)->get($url, $data)->json();
}
public function post($url, $data = null)
{
return Http::withToken($this->token)->post($url, $data)->json();
}
}
It's okay even if you don't attach any code, maybe just guide me a bit what would be the best way to achieve something like this.
The chain methods that you want to apply it's called the Builder pattern
Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.
you can learn and find snippets from here https://refactoring.guru/design-patterns/builder
back to your case, I cant agree that we need the builder pattern here, but let's try to have the small steps with your code, let's say you want to build Shiprocket object that contains the Order and the Shipment
the simple change you need is to return the Shiprocket so the code should look like this
<?php
class Shiprocket
{
protected $credentials;
protected $token;
private $order;
private $shipment;
public function withCredential($credential_id)
{
$this->credentials = config('shiprocket.credentials')[$credential_id];
$this->token = $this->getToken();
$this->order = null;
$this->shipment = null;
return $this;
}
public function __construct()
{
$this->credentials = config('shiprocket.credentials')[config('shiprocket.default_credentials')];
$this->token = $this->getToken();
$this->order = null;
$this->shipment = null;
}
public function order($order_id = null)
{
$this->order = new OrderResource($order_id);
return $this;
}
public function shipment($shipment_id = null)
{
$this->shipment = new ShipmentResource($shipment_id);
return $this;
}
public function getOrder(){
return $this->order;
}
public function getShipment(){
return $this->shipment;
}
public function getToken(): string
{
$duration = config('shiprocket.token_cache') ? config('shiprocket.token_cache_duration') : 0;
return cache()->remember("shiprocket-{$this->credentials['email']}", $duration, function () {
return Http::post("https://apiv2.shiprocket.in/v1/external/auth/login", [
'email' => $this->credentials['email'],
'password' => $this->credentials['password'],
])->json()['token'];
});
}
public function get($url, $data = null)
{
return Http::withToken($this->token)->get($url, $data)->json();
}
public function post($url, $data = null)
{
return Http::withToken($this->token)->post($url, $data)->json();
}
}
Note: the code could not be perfect when it comes to the standard and the best practice I just change it to follow your idea
I hope it's helpful
Am using fractal package from phpleague. I have a transform class setup like this
class ConversationTransformer extends TransformerAbstract
{
public function transform (Conversation $conversation, $user)
{
return [];
}
}
however i get missing argument 2 exception for transform when i try to access it
$user = $this->people->get($this->user());
//conversations
$conversations = $this->conversations->user($user);
return $this->fractal->paginatedCollection($conversations, $user, new ConversationTransformer());
class ConversationTransformer extends TransformerAbstract
{
private $params = [];
function __construct($params = [])
{
$this->params = $params;
}
public function transform (Conversation $conversation)
{
//-- $this->params will be used here
return [];
}
}
Call it with this return:
return $this->fractal->paginatedCollection($conversations, new ConversationTransformer(['user' => $user]))
can somebody try to explain me how to use multiple normalizers when serializing data from multiple classes with the Symfony serializer?
Lets say that I have the following classes:
class User
{
private $name;
private $books;
public function __construct()
{
$this->books = new ArrayCollection();
}
// getters and setters
}
class Book
{
private $title;
public function getTitle()
{
return $this->title;
}
public function setTitle($title)
{
$this->title = $title;
}
}
And I want to serialize an user who has multiple books.
$first = new Book();
$first->setTitle('First book');
$second = new Book();
$second->setTitle('Second book');
$user = new User();
$user->setName('Person name');
$user->addBook($first);
$user->addBook($second);
dump($this->get('serializer')->serialize($user, 'json'));
die();
Let's say that I also want to include a hash when serializing a book, so I have the following normalizer:
class BookNormalizer implements NormalizerInterface
{
public function normalize($object, $format = null, array $context = array())
{
return [
'title' => $object->getTitle(),
'hash' => md5($object->getTitle())
];
}
public function supportsNormalization($data, $format = null)
{
return $data instanceof Book;
}
}
And I am getting the expected result:
{"name":"Person name","books":[{"title":"First book","hash":"a9c04245e768bc5bedd57ebd62a6309e"},{"title":"Second book","hash":"c431a001cb16a82a937579a50ea12e51"}]}
The problem comes when I also add a normalizer for the User class:
class UserNormalizer implements NormalizerInterface
{
public function normalize($object, $format = null, array $context = array())
{
return [
'name' => $object->getName(),
'books' => $object->getBooks()
];
}
public function supportsNormalization($data, $format = null)
{
return $data instanceof User;
}
}
Now, the books aren't normalized using the previously given normalizer, and i get the following:
{"name":"Person name","books":[{},{}]}
I tried to find a way (documentation and other articles) to always call the normalizers for the given types (eg. always call the book normalizer when the type is Book, even if the data is nested and used in another normalizer) but could not succeed.
I think i have misunderstood something about normalizers but don't know what. Can somebody explain to is what i want possible and how to do it?
You have to use the NormalizerAwareTrait so you can access the normalizer for books
add interface
use trait
call normalize() method for books
code:
class UserNormalizer implements NormalizerInterface, NormalizerAwareInterface
{
use NormalizerAwareTrait;
public function normalize($object, $format = null, array $context = array())
{
return [
'name' => $object->getName(),
'books' => $this->normalizer->normalize($object->getBooks(), $format, $context)
];
}
public function supportsNormalization($data, $format = null)
{
return $data instanceof User;
}
}
There are lots of articles regarding factory method implementation in PHP.
I want to implement such a method for my MongoDB implementation in PHP.
I wrote the code something like below. Please Look at that code.
<?php
class Document {
public $value = array();
function __construct($doc = array()) {
$this->value = $doc;
}
/** User defined functions here **/
}
class Collection extends Document {
//initialize database
function __construct() {
global $mongo;
$this->db = Collection::$DB_NAME;
}
//select collection in database
public function changeCollection($name) {
$this->collection = $this->db->selectCollection($name);
}
//user defined method
public function findOne($query = array(), $projection = array()) {
$doc = $this->collection->findOne($query, $projection);
return isset($doc) ? new Document($doc) : false;
}
public function find($query = array(), $projection = array()) {
$result = array();
$cur = $this->collection->find($query, $projection);
foreach($cur as $doc) {
array_push($result, new Document($doc));
}
return $result;
}
/* Other user defined methods will go here */
}
/* Factory class for collection */
class CollectionFactory {
private static $engine;
private function __construct($name) {}
private function __destruct() {}
private function __clone() {}
public static function invokeMethod($collection, $name, $params) {
static $initialized = false;
if (!$initialized) {
self::$engine = new Collection($collection);
$initialized = true;
}
self::$engine->changeCollection($collection);
return call_user_func_array(array(self::$engine, $name), $params);
}
}
/* books collection */
class Books extends CollectionFactory {
public static function __callStatic($name, $params) {
return parent::invokeMethod('books', $name, $params);
}
}
/* authors collection */
class Authors extends CollectionFactory {
public static function __callStatic($name, $params) {
return parent::invokeMethod('authors', $name, $params);
}
}
/* How to use */
$books = Books::findOne(array('name' => 'Google'));
$authors = Authors::findOne(array('name' => 'John'));
Authors::update(array('name' => 'John'), array('name' => 'John White'));
Authors::remove(array('name' => 'John'));
?>
My questions are:-
Is this correct PHP implementation of Factory method?
Does this implementation have any issues?
Are there any better methodologies over this for this scenario?
Thanks all for the answers.
Hmm no, because with your piece of code you make ALL methods on the collection class available for a static call. That's not the purpose of the (abstract) factory pattern.
(Magic) methods like __callStatic or call_user_func_array are very tricky because a developer can use it to call every method.
What would you really like to do? Implement the factory pattern OR use static one-liner methods for your MongoDB implementation?!
If the implementation of the book and author collection has different methods(lets say getName() etc..) I recommend something like this:
class BookCollection extends Collection {
protected $collection = 'book';
public function getName() {
return 'Book!';
}
}
class AuthorCollection extends Collection {
protected $collection = 'author';
public function getName() {
return 'Author!';
}
}
class Collection {
private $adapter = null;
public function __construct() {
$this->getAdapter()->selectCollection($this->collection);
}
public function findOne($query = array(), $projection = array()) {
$doc = $this->getAdapter()->findOne($query, $projection);
return isset($doc) ? new Document($doc) : false;
}
public function getAdapter() {
// some get/set dep.injection for mongo
if(isset($this->adapter)) {
return $this->adapter;
}
return new Mongo();
}
}
class CollectionFactory {
public static function build($collection)
{
switch($collection) {
case 'book':
return new BookCollection();
break;
case 'author':
return new AuthorCollection();
break;
}
// or use reflection magic
}
}
$bookCollection = CollectionFactory::build('book');
$bookCollection->findOne(array('name' => 'Google'));
print $bookCollection->getName(); // Book!
Edit: An example with static one-liner methods
class BookCollection extends Collection {
protected static $name = 'book';
}
class AuthorCollection extends Collection {
protected static $name = 'author';
}
class Collection {
private static $adapter;
public static function setAdapter($adapter) {
self::$adapter = $adapter;
}
public static function getCollectionName() {
$self = new static();
return $self::$name;
}
public function findOne($query = array(), $projection = array()) {
self::$adapter->selectCollection(self::getCollectionName());
$doc = self::$adapter->findOne($query, $projection);
return $doc;
}
}
Collection::setAdapter(new Mongo()); //initiate mongo adapter (once)
BookCollection::findOne(array('name' => 'Google'));
AuthorCollection::findOne(array('name' => 'John'));
Does it make sense for Collection to extend Document? It seems to me like a Collection could have Document(s), but not be a Document... So I would say this code looks a bit tangled.
Also, with the factory method, you really want to use that to instantiate a different concrete subclass of either Document or Collection. Let's suppose you've only ever got one type of Collection for ease of conversation; then your factory class needs only focus on the different Document subclasses.
So you might have a Document class that expects a raw array representing a single document.
class Document
{
private $_aRawDoc;
public function __construct(array $aRawDoc)
{
$this->_aRawDoc = $aRawDoc;
}
// Common Document methods here..
}
Then specialized subclasses for given Document types
class Book extends Document
{
// Specialized Book functions ...
}
For the factory class you'll need something that will then wrap your raw results as they are read off the cursor. PDO let's you do this out of the box (see the $className parameter of PDOStatement::fetchObject for example), but we'll need to use a decorator since PHP doesn't let us get as fancy with the Mongo extension.
class MongoCursorDecorator implements MongoCursorInterface, Iterator
{
private $_sDocClass; // Document class to be used
private $_oCursor; // Underlying MongoCursor instance
private $_aDataObjects = []; // Concrete Document instances
// Decorate the MongoCursor, so we can wrap the results
public function __construct(MongoCursor $oCursor, $sDocClass)
{
$this->_oCursor = $oCursor;
$this->_sDocClass = $sDocClass;
}
// Delegate to most of the stock MongoCursor methods
public function __call($sMethod, array $aParams)
{
return call_user_func_array([$this->_oCursor, $sMethod], $aParams);
}
// Wrap the raw results by our Document classes
public function current()
{
$key = $this->key();
if(!isset($this->_aDataObjects[$key]))
$this->_aDataObjects[$key] =
new $this->sDocClass(parent::current());
return $this->_aDataObjects[$key];
}
}
Now a sample of how you would query mongo for books by a given author
$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoCollection($db, 'book');
// search for author
$bookQuery = array('Author' => 'JR Tolken');
$cursor = $collection->find($bookQuery);
// Wrap the native cursor by our Decorator
$cursor = new MongoCursorDecorator($cursor, 'Book');
foreach ($cursor as $doc) {
var_dump($doc); // This will now be an instance of Book
}
You could tighten it up a bit with a MongoCollection subclass and you may as well have it anyway, since you'll want the findOne method decorating those raw results too.
class MongoDocCollection extends MongoCollection
{
public function find(array $query=[], array $fields=[])
{
// The Document class name is based on the collection name
$sDocClass = ucfirst($this->getName());
$cursor = parent::find($query, $fields);
$cursor = new MongoCursorDecorator($cursor, $sDocClass);
return $cursor;
}
public function findOne(
array $query=[], array $fields=[], array $options=[]
) {
$sDocClass = ucfirst($this->getName());
return new $sDocClass(parent::findOne($query, $fields, $options));
}
}
Then our sample usage becomes
$m = new MongoClient();
$db = $m->selectDB('test');
$collection = new MongoDocCollection($db, 'book');
// search for author
$bookQuery = array('Author' => 'JR Tolken');
$cursor = $collection->find($bookQuery);
foreach($cursor as $doc) {
var_dump($doc); // This will now be an instance of Book
}
Recently I made my first ZF2 application. I was walking through the code to see if I could make the code somewhat cleaner. Then I noticed that my controller classes have a huge block of code that supplies the controller of the TableGateway classes it needs. And I wondered is there a shorter/cleaner way to do this? It just seems silly that half of my controller class is dedicated to this simple task of fetching some TableGateWay classes.
protected $appointmentTable;
protected $customerTable;
protected $serviceTable;
protected $locationTable;
// ... some action methods that actually do the work.
public function getAppointmentTable()
{
if (!$this->appointmentTable) {
$sm = $this->getServiceLocator();
$this->appointmentTable = $sm->get('Appointment\Model\AppointmentTable');
}
return $this->appointmentTable;
}
public function getServiceTable()
{
if (!$this->serviceTable) {
$sm = $this->getServiceLocator();
$this->serviceTable = $sm->get('Appointment\Model\ServiceTable');
}
return $this->serviceTable;
}
public function getLocationTable()
{
if (!$this->locationTable) {
$sm = $this->getServiceLocator();
$this->locationTable = $sm->get('Appointment\Model\LocationTable');
}
return $this->locationTable;
}
public function getCustomerTable()
{
if (!$this->customerTable) {
$sm = $this->getServiceLocator();
$this->customerTable = $sm->get('Customer\Model\CustomerTable');
}
return $this->customerTable;
}
The way your Controllers should ideally be set up is through the means of proper(!) dependency injection. In Zend Framework 2 you have two main ways to declare controllers within the ControllerManager. The first one being invokables for controllers who have no dependencies and the second one being factories for controllers who have dependencies.
Any TableGateway always is a dependency. To my experience there are no controllers who are invokables at all :P
There's two ways to set up controller factories.
Module.php using getControllerConfig()
Under the controllers[factories] key in your module.config.php using Factory-Classes
For simplicity I'll choose the first approach now:
public function getControllerConfig()
{
return array(
'factories' => array(
'My\Foo\Controller' => function ($cpm) {
//#var $cpm \Zend\Mvc\Controller\ControllerManager
$serviceLocator = $cpm->getServiceLocator();
$tableGateway = $serviceLocator->get('My\Table\Gateway');
return new \My\Foo\Controller($tableGateway);
}
)
);
}
With this, all that's left is for you to modify your controller and have it pass the respective tablegateway inside its constructor:
class Controller
{
protected $tableGateway;
public function __construct(\My\Table\Gateway $tg)
{
$this->tableGateway = $tg;
}
public function indexAction()
{
return new ViewModel(array(
'entries' => $this->tableGateway->select()
));
}
}
And that's all there is to it. It's all about proper dependency injection that makes your life ultimately so much easier.
Obviously this example only covers one table, but you can do the same just passing more tables through the constructor. That is: only if you really need ALL TableGateways in there (which sounds a bit fishy) ;)
Could you just simplify the process in another method? I'm not aware of this function in Zend2, but still, if there is no method on framework level, you can write your own simplified method
My test so far:
public function setTable($method) {
$method = lcfirst(str_replace("get", "", $method));
$this->$method = 'Apointment\Model\\'.ucfirst($method);
return $this->$method;
}
public function getLocationTable() {
$this->setTable(__FUNCTION__);
var_dump(get_object_vars($this));
}
Outputs:
array (size=1)
'locationTable' => string 'Apointment\Model\LocationTable' (length=30)
So you can change setTable() method to use your set() proxy:
public function setTable($method) {
$method = lcfirst(str_replace("get", "", $method));
if (!$this->$method) {
$sm = $this->getServiceLocator();
$this->$method = $sm->get('Apointment\Model\\'.ucfirst($method));
}
return $this->$method;
}
public function getLocationTable() {
return $this->setTable(__FUNCTION__);
}
public function getServiceTable() {
return $this->setTable(__FUNCTION__);
}
Or you can get all your tables in array, iterate through it and pass the name to your setTable() method, which will set inner properties.
My string test (because I don't have ZF2 right here, and testing if the proper string which you are passing to the set() proxy is built:
class Tables {
public function setTable($method) {
$method = lcfirst(str_replace("get", "", $method));
$this->$method = 'Apointment\Model\\'.ucfirst($method);
/*if (!$this->$method) {
$sm = $this->getServiceLocator();
$this->$method = $sm->get('Apointment\Model\\'.ucfirst($method));
}*/
return $this->$method;
}
public function getLocationTable() {
return $this->locationTable;
}
public function getServiceTable() {
return $this->serviceTable;
}
public function getAppointmentTable() {
return $this->appointmentTable;
}
public function setAllTables() {
foreach (get_class_methods(__CLASS__) as $method) {
if (strpos($method, 'get')!== false && strpos($method, 'Table')!==false)
$this->setTable($method);
}
}
}
$tables = new Tables();
$tables->setAllTables();
var_dump(get_object_vars(($tables)));
Outputs:
array (size=3)
'locationTable' => string 'Apointment\Model\LocationTable' (length=30)
'serviceTable' => string 'Apointment\Model\ServiceTable' (length=29)
'appointmentTable' => string 'Apointment\Model\AppointmentTable' (length=33)
Now all your get____Table() methods are valid getters. E.g.:
var_dump($tables->getServiceTable());
returns
string 'Apointment\Model\ServiceTable' (length=29)