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
Related
I can not load data to properties using this construction I receive null in dump
<?php
namespace App\Domain\Good;
class GoodDto
{
public $name;
public $articul;
public $price;
public $type;
public $qnt;
public $discount;
public $category;
public $description;
public $description2;
public $color;
public function load($data)
{
$this->name = $data['name'];
$this->articul = $data['artikul'];
$this->price = $data['price'];
$this->type = (isset($data['type'])) ? $data['type'] : null;
$this->qnt = $data['count'];
$this->discount = $data['spinner-decimal'];
$this->category = $data['id_cat'];
$this->description = $data['editor1'];
$this->description2 = '';
$this->color = $data['color'];
//$this->user_id = Auth::user()->id;
}
public static function fromRequest($request)
{
dump('inp=>',(new self ())->load($request->input()));
return (new self ())->load($request->input());
}
}
Please explain to me why I receive null while request->input() is an array, I call it from another place
$dto=GoodDto::fromRequest($request);
Method chaining, returns the last return from the chain. The other returns are used to call the next link in the chain.
(new self ())->load()
So load() needs to return $this
public function load($data)
{
...
return $this;
}
Currently it returns null, which is why it returns null.
See you are not saving the instance from the constructor, instead you pass it to load by enclosing it within the (....). By pass it I mean you call the load method on the return from the constructor.
You can test this like so:
class foo{
function load(){
return $this;//return this
}
}
var_dump((new foo)->load());
class bar{
function load(){
//return null
}
}
var_dump((new bar)->load());
Output
//return this
object(foo)#1 (0) {
}
//return null
NULL
sandbox
The second class in the example above class bar, is essentially what you are doing.
PS. forgot to scroll down on your post at first ... lol ... So I had to update my answer.
Bonus
You can also simplify the load code like this:
public function load($data)
{
foreach($data as $prop=>$value){
if(property_exists($this,$prop)) $this->$prop = $value;
}
return $this;
}
This way if you add new properties you don't have to edit the load method ever again, you just have to name the array elements the same as the class properties. You can even throw an error if the property does not exist if you want, by adding an else to the condition etc...
Personally, when I do this I prefer to call a set method like this:
//eg. $data = ['foo' => '2019-06-16']
public function load(array $data)
{
foreach($data as $prop=>$value){
$method = 'set'.$prop; //$method = 'setfoo' using the example above
if(method_exists($this,$method )){
$this->$method($value); //calls 'setfoo' with '2019-06-16'
}else{
throw new Exception('Unknown method '.$method);
}
}
return $this;
}
public function setFoo($date){
$this->foo = new DateTime($date);
}
Then you can apply some transforms to the data etc... PHP method names are not case sensitive. You can even combine these by first checking for a method then a property then throw the error etc...
Cheers.
I want to be able to use an object like below, to retrieve new orders and new invoices. I feel like it is most readable, but I am having trouble writing the PHP class to work this way.
$amazon = new Amazon();
$amazon->orders('New')->get();
$amazon->invoices('New')->get();
In my PHP class, how would my get() method be able to distinguish whether to return orders or invoices?
<?php
namespace App\Vendors;
class Amazon
{
private $api_key;
public $orders;
public $invoices;
public function __construct()
{
$this->api_key = config('api.key.amazon');
}
public function orders($status = null)
{
$this->orders = 'orders123';
return $this;
}
public function invoices($status = null)
{
$this->invoices = 'invoices123';
return $this;
}
public function get()
{
// what is the best way to return order or invoice property
// when method is chained?
}
}
A couple of ways, if you want it dynamic and don't do any logic in the methods, use something like __call
<?php
class Amazon {
public $type;
public $method;
public function get()
{
// do logic
// ...
return 'Fetching: '.$this->method.' ['.$this->type.']';
}
public function __call($method, $type)
{
$this->method = $method;
$this->type = $type[0];
return $this;
}
}
$amazon = new Amazon();
echo $amazon->orders('New')->get();
echo $amazon->invoices('New')->get();
If you want to do logic in the methods, do something like:
<?php
class Amazon {
public $type;
public $method;
public function get()
{
return 'Fetching: '.$this->method.' ['.$this->type.']';
}
public function orders($type)
{
$this->method = 'orders';
$this->type = $type;
// do logic
// ...
return $this;
}
public function invoices($type)
{
$this->method = 'invoices';
$this->type = $type;
// do logic
// ...
return $this;
}
}
$amazon = new Amazon();
echo $amazon->orders('New')->get();
echo $amazon->invoices('New')->get();
As orders and invoices are set methods, I would suggest to do as follows:
public function get(array $elements)
{
$result = [];
foreach($elements as $element) {
$result[$element] = $this->$element;
}
return $result;
}
So, you can call get method as:
$amazon = new Amazon();
$amazon->orders('New')->invoices('New')->get(['orders', 'invoices']);
** You need to validate the element's availability within the get method.
Is there a way to use a object variable instantiated from a class in two functions?
Here's the code I've tried, but its just returning null:
class bookAppointmentsController extends APIController
{
private $business;
public funcition check($key)
{
$this->business = new APIClass();
$setconnection = $this->business->connectAPI($key);
}
public function book()
{
dd($this->business) //returns null
$this->business->book();
}
}
I am trying to use the $business object in two functions but it does not work, when I dd($business) it returns null
Any way to do this?
Move the instantiation to the constructor:
public function __construct(APIClass $business)
{
$this->business = $business;
}
However, it would be better if you make Laravel do the heavy lifting and prepare the APIClass for you.
In your AppServicePorvider under the register method, you can create the APIClass
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind('APIClass', function ($app) {
$api = new APIClass();
// Do any logic required to prepare and check the api
$key = config('API_KEY');
$api->connectAPI($key);
return $api;
});
}
Check the documentations for more details.
Maybe the solution could be to make the variable Global
You could make the variable global:
function method( $args ) {
global $newVar;
$newVar = "Something";
}
function second_method() {
global $newVar;
echo $newVar;
}
Or you could return it from the first method and use it in the second method
public function check($key)
{
$this->business = new APIClass();
$setconnection = $this->business->connectAPI($key);
return $this->business;
}
public function book()
{
$business = check($key);
$business->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)
I want to create a function in a class that is available for a set of users, but that they won't be able to access. Ex:
class Stuff_for_user {
private $errors;
/*
* private $errors gets modified by private functions
*/
public function get_errors(){ // This is for users to display errors.
return $this->errors;
}
/*something here...*/ function set_errors($str){
$this->errors = $str;
}
}
So far so good, but now I want the parent class to be able to set Stuff_for_User's errors:
class Main_mess {
public index(){
$user_available_data = new Stuff_for_user();
if($big_error)
$user_available_data->set_errors("BIG ERROR!!!");
$this->send_to_users($user_available_data);
}
}
I want only Main_mess to be able to access Stuff_for_User's set_errors() method. Is that possible?
No, that is not possible like that, since Main_mess is not a parent class of Stuff_for_users (and this is probably what you want, looking at what your code actually does). So set_errors has to be public if you want to call it from the outside.
This is not possible how you want to implement it.
Some ideas (i dont know why or how you want to do that but just ideas...):
do set_error($str,$access_key) and let $access_key be an access string only you know!
let Stuff_for_user be in Extended_Stuff_for_user which has the set_error function like:
class Extended_Stuff_for_user {
private $errors;
private $Stuff_for_user;
public function set_errors() {
/* ... */
}
public function getStuffForUser() {
return $this->Stuff_for_user;
}
}
It seems that you are looking for implementation of something called friend class in php. Well .. i'm sorry to tell you this, but it is not possible.
You should look at other possible solutions to your problem.
class SecureContainer{
protected $user = null;
protected $target = null;
public function __construct( $target, $user )
{
$this->target = $target;
$this->user = $user;
}
public function __call( $method, $arguments )
{
if ( $this->user->isAllowed(getType( $this->target ), $method))
{
return call_user_func_array(
array( $this->target, $method), $arguments );
}
}
}
Use it like this:
$something = new UnsecureSomething;
$user = new User( $uid );
$something = new SecureContainer( $something, $user );
This should let you control the access to methods.
Yes it possible but it can be dirty.
Like This.
class Stuff_for_user {
private $errors;
/*
* private $errors gets modified by private functions
*/
public function get_errors(){ // This is for users to display errors.
return $this->errors;
}
/*
This way the child classes of Main will able be to use the set_errors function;
*/
function set_errors($class,$str){
if($class instanceof Main_mess)
{
$this->errors = $str;
}
/*
AndThis way the only Main_mess will be able;
*/
function set_errors($class,$str){
if(get_class($class)=="Main_mess")
{
$this->errors = $str;
}
}
class Main_mess {
public index(){
$user_available_data = new Stuff_for_user();
if($big_error)
$user_available_data->set_errors($this,"BIG ERROR!!!");
$this->send_to_users($user_available_data);
}
}