How to avoid creating function/method with many parameters? - php

Supposed I have method of repository class like this to create post:
public function create($title, $slug, $custom_slug = '', $meta_keyword, $body, $meta_description, $status, $bla, $bla)
is there any way to avoid this in the right way? because I think it is not good if there are many parameters and not really readable. i often put the value in wrong place since i dont remember the parameters orders.
if I set parameter as array like this:
public function create(array $columns)
but client won't know which required parameters and which one is not.

In general, you can get around this problem by passing in your arguments as an array. Alternatively, you can make the parameters public static fields, so that they can be accessed from within your method without you having to even pass them in as parameters.

You can create a class like this:
class Post {
protected $title;
protected $body;
protected $slug;
protected $metaKeyword;
protected $metaDescription;
protected $status;
function __construct($title, $body) {
$this->setTitle($title);
$this->setBody($body);
}
public function setTitle($title) {
$this->title = $title;
}
public function setBody($body) {
$this->body = $body;
}
public function setSlug($slug) {
$this->slug = $slug;
}
public function setMetaKeyword($metaKeyword) {
$this->metaKeyword = $metaKeyword;
}
public function setMetaDescription($metaDescription) {
$this->metaDescription = $metaDescription;
}
public function setStatus($status) {
$this->status = $status;
}
public function save() {
// do something here
}
}
$post = new Post('Example', 'This is a body');
$post->setSlug('example-post');
$post->save();

Related

Laravel Packge Nested Classes and Methods structure in Object-oriented PHP

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

Some more syntactic sugar with the "with" statement?

I like PHP, but I miss some of the constructs from other languages that although don't do anything for performance, make the code look cleaner and possibly more maintainable. I'm thinking of Visual Basic days and the "with" statement.
So ideally in PHP we could do this:
with($myWellDescribedInstance) {
->property1="string";
->property2=1;
->property3=2;
->myMethod();
}
Instead of
$myWellDescribedInstance->property1="string";
$myWellDescribedInstance->property2=1;
$myWellDescribedInstance->property3=2;
$myWellDescribedInstance->myMethod();
Is there anything like this in PHP?
You can implement a fluent interface on any class just by having a function return $this.
This is mostly used for setters, but of course it works for any method for which you would normally not have a return value.
For example:
class Person
{
protected $name = '';
protected $surname = '';
protected $email = '';
public function getName()
{
return $this->name;
}
public function getSurname()
{
return $this->surname;
}
public function getEmail()
{
return $this->email;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function setSurname($surname)
{
$this->surname = $surname;
return $this;
}
public function setEmail($email)
{
$this->email = $email;
return $this;
}
}
Usage:
$person = new Person;
$person->setName('John')
->setSurname('Doe')
->setEmail('johndoe#email.com');
Of course, calling the method (for example) setName or withName would be entirely up to you.
Another idea might be to have both a setName method (which doesn't return anything) and a withName method (which returns $this), but that might be a bit of an overkill.
If you use "setters" instead of direct property access you can chain methods.
class A {
private $a;
private $b;
public function setA($a)
{
$this->a = $a;
return $this;
}
public function setB($b)
{
$this->b = $b;
return $this;
}
public function doSomething()
{}
}
$a = new A();
$a->setA('a')
->setB('b')
->doSomething();

Optional parameters on request class

What is the proper way to handle optional parameters on a service request?
Lets say in this scenario i want to have also $title as optional parameter
<?php
namespace Lw\Application\Service\Wish;
class AddWishRequest
{
private $userId;
private $email;
private $content;
public function __construct($userId, $email, $content)
{
$this->userId = $userId;
$this->email = $email;
$this->content = $content;
}
public function userId()
{
return $this->userId;
}
public function email()
{
return $this->email;
}
public function content()
{
return $this->content;
}
}
Example from here
Usually in DDD and following the rules of clean code also, if you have optional parameters, you have multiple constructors, two in this case:
One for just the mandatory arguments.
One for all the arguments including the optional but in this constructor it would be mandatory too.
If you wanna construct the object without the optional argument you call the first one. And if you wanna supply a non null optional argument you use the second one.
Usually you should use factory methods with meaningful names, and hide the constructors.
AddWishRequest.create ( userId, email, content)
AddWishRequest.createWithTitle ( userId, email, content, title )
You can use optional arguments in any function call, also the constructor. Best practice is, to preceed "get" to the getters.
public function __construct($userId, $email, $content, $title = "")
means, $title is an optional argument. When not supplied, it is set to an empty string. You also could provide any other type or value.
namespace Lw\Application\Service\Wish;
class AddWishRequest
{
private $userId;
private $email;
private $content;
private $title;
public function __construct($userId, $email, $content, $title = "")
{
$this->userId = $userId;
$this->email = $email;
$this->content = $content;
$this->title = $title;
}
public function getUserId()
{
return $this->userId;
}
public function getEmail()
{
return $this->email;
}
public function getContent()
{
return $this->content;
}
public function getTitle()
{
return $this->title;
}
}
Update
If you just declare a property like
private $property
then accessing it via $this->property with always be null (until you set a value). You should make the getter responsible for returning the correct values.
Following example will always return an array making use of the NULL-coalesce operator:
if $something is true (or has an array content) will return $something
else will return empty array
public function getSomething() : array {
return $this->something ?? [];
}

How to use Closure as an Anonymous Function like on Javascript?

I have a question, I didn't clearly understand what Closures uses on OOP, but I did something like this:
<?php /** * */
class Xsample {
public static $name;
public static $address = array("Mandaluyong", "City");
public static function setName ($name) {
self::$name = $name;
}
public static function getName() {
echo self::$name;
}
public static function sub ($func) {
return call_user_func_array($func, self::$address);
}
}
Xsample::setName("Eric");
Xsample::sub(function ($address) {
echo $address;
});
?>
and it echo "Mandaluyong".
I'm expecting that it'll return an array from Xsample::$address but it didn't. Could someone please explain this to me?
call_user_func_array passes the 2nd argument's elements as paramters to the function being called. so if your function had another parameter it will work.
Xsample::sub(function ($address, $address2) {
echo $address;
echo $address2;
});

Class function only available to "parent" - PHP

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

Categories