Laravel model observer repository injection - php

I'm trying to wrap my head around how to inject Laravel's model observers with repositories.
Currently, I have this setup.
UserPetServiceProvider.php
<?php namespace Bunny\Providers;
use Illuminate\Support\ServiceProvider;
use Bunny\Observers\Pet\UserPetObserver;
class UserPetServiceProvider extends ServiceProvider {
public function register()
{
// User pets
$this->app->bind('Bunny\Repos\Pet\IUserPetRepo', 'Bunny\Repos\Pet\UserPetRepo');
// User pet layers
$this->app->bind('Bunny\Repos\Pet\IUserPetLayerRepo', 'Bunny\Repos\Pet\UserPetLayerRepo');
// User pet markings
$this->app->bind('Bunny\Repos\Pet\IUserPetMarkingRepo', 'Bunny\Repos\Pet\UserPetMarkingRepo');
$this->app->events->subscribe(new UserPetObserver());
}
}
It binds all the interfaces and repositories fine and would with the observer, but I need repository injection which I do in the constructor. Nothing is being passed in the constructor so it would explain why it fails.
UserPetRepo.php
<?php namespace Bunny\Repos\Pet;
use Bunny\Repos\BaseRepo;
use Bunny\Models\Pet\UserPet;
use Bunny\Repos\User\IUserRepo;
use Bunny\Repos\Breed\IStageRepo;
use Bunny\Repos\Breed\IBreedLayerRepo;
use Illuminate\Config\Repository as Config;
use Illuminate\Support\Str as String;
use Illuminate\Session\SessionManager as Session;
use Illuminate\Events\Dispatcher;
class UserPetRepo extends BaseRepo implements IUserPetRepo {
public function __construct(UserPet $pet, IUserRepo $user, IStageRepo $stage, IBreedLayerRepo $layer, Config $config, String $string, Session $session, Dispatcher $events)
{
$this->model = $pet;
$this->user = $user;
$this->stage = $stage;
$this->layer = $layer;
$this->config = $config;
$this->string = $string;
$this->session = $session;
$this->events = $events;
$this->directory = $this->config->get('pathurl.userpets');
$this->url = $this->config->get('pathurl.userpets_url');
}
/**
* Create new user pet
* #param attributes Attributes
*/
public function createWithImage( array $attributes, array $colors, $domMarkings = null, $domMarkingColors = null, $recMarkings = null, $recMarkingColors = null )
{
$this->model->name = $attributes['name'];
$this->model->breed_id = $attributes['breed_id'];
$this->model->user_id = $this->user->getId();
$this->model->stage_id = $this->stage->getBaby()->id;
// Get image
$image = $this->layer->compile(
$attributes['breed_id'],
'baby',
$colors,
$domMarkings,
$domMarkingColors
);
// Write image and set
$file = $this->writeImage( $image );
if( ! $file )
{
return false;
}
$this->model->base_image = $file;
$this->model->image = $file;
if( ! $this->model->save() )
{
return false;
}
$this->events->fire('userpet.create', array($this->model));
return $this->model;
}
/**
* Write image
* #param image Imagick Object
*/
protected function writeImage( $image )
{
$fileName = $this->string->random(50) . '.png';
if( $image->writeImage( $this->directory . $fileName ) )
{
return $fileName;
}
$this->model->errors()->add('writeImage', 'There was an error writing your image. Please contact an administrator');
return false;
}
}
UserPetObserver.php
use Bunny\Repos\Pet\IUserPetLayerRepo;
use Bunny\Repos\Pet\IUserPetMarkingRepo;
use Bunny\Observers\BaseObserver;
class UserPetObserver extends BaseObserver {
public function __construct(IUserPetLayerRepo $layers, IUserPetMarkingRepo $markings)
{
$this->layers = $layers;
$this->markings = $markings;
}
/**
* After create
*/
public function onCreate( $model )
{
$this->layers->user_pet_id = $model->id;
dd(Input::all());
$this->layers->breed_layer_id = $model->id;
}
public function subscribe( $event )
{
$event->listen('userpet.create', 'UserPetObserver#onCreate');
}
}
It throws this as the error:
Argument 1 passed to
Bunny\Observers\Pet\UserPetObserver::__construct() must be an instance
of Bunny\Repos\Pet\IUserPetLayerRepo, none given, called in H:\WD
SmartWare.swstor\HALEY-HP\Source2\bunny-meadows\app\Bunny\Providers\UserPetServiceProvider.php
on line 22 and defined
Which makes sense since I'm not passing anything in the constructor. So I try to pass my repository manually.
$this->app->events->subscribe(new UserPetObserver(new UserPetLayerRepo, new UserPetMarkingRepo));
But then it throws errors of UserPetLayerRepo needing injections... and it just chains on and on. Is there anyway of doing this that I'm just overthinking?
Thanks.
EDIT:::
This is the only thing I could think of doing. This seems like a really ugly/bad way of doing it though:
$this->app->events->subscribe(new UserPetObserver($this->app->make('Bunny\Repos\Pet\UserPetLayerRepo'), $this->app->make('Bunny\Repos\Pet\UserPetMarkingRepo')));
Any other ideas?

Try just:
$this->app->events->subscribe($this->app->make('UserPetObserver'));
When Laravel makes the UserPetObserver object, it will read the type-hinted dependencies in the constructor and automatically make them, as well.

Related

Connecting method/function in laravel

I'm trying to create a class function which resembles how we used to fetch database listing and convert into a dropdown listing.
eg: DB::table()->where()->get()
what i would like to achieve in laravel custom class or through model is this
Dropdown::fetch()->toArray()
Dropdown::fetch()->toDropdown()
I tried to figure out how this can be done through google. But couldn't find any solution to it.
I'm using laravel 5.8
--
Edit - Sample Code added
Code tried:
namespace App\Http\Models;
use DB;
use Closure;
use BadMethodCallException;
use Illuminate\Support\Traits\Macroable;
use Illuminate\Database\Eloquent\Model;
class Dropdown extends Model
{
private $result = [];
private $default;
public function _cities(){
$tbl_cities = config("tables.TBL_meta_cities");
$result = DB::table($tbl_cities)->select('id', 'cityname')
->orderBy('id')->get()->toArray();
$this->result = $result;
}
public function _select(){
}
public function _list(){
return $this->result;
}
public function _setDefault($def=''){
}
public static function __callStatic($method, $parameters)
{
$action = '_'.$method;
if(method_exists(get_called_class(), $action))
self::$action(...$parameters);
else echo 'not found';
}
public function __call($method, $parameters)
{
$action = '_'.$method;
if(method_exists($get_called_class(), $action))
self::$action(...$parameters);
else echo 'not found';
}
}
and i tried
Dropdown::cities()->list()
but ended with bugs
Well i figured it out myself.
class Dropdown extends Model
{
private static $result = [];
private function getCities(){
$result = City::select('id', 'cityname')
->orderBy('id')->get()->toArray();
self::$result = $result;
}
public function toArray(){
return self::$result;
}
public function toDropdown(){
// Do the dropdown works
}
/**
* Dynamically handle calls to the class.
*
* #param string $method
* #param array $parameters
* #return mixed
*
* #throws \BadMethodCallException
*/
public function __callMethod($method, $parameters){
// Check with inclusive
$class = get_called_class();
$avail = false;
$action = '';
// Check method availability - direct
if(!$avail){
$action = $method;
$avail = method_exists($class, $action);
}
// Check method 2
if(!$avail){
$action = 'get'.ucwords($method);
$avail = method_exists($class, $action);
}
if($avail){
// Call the method
$return = self::$action(...$parameters);
if(!empty($return)) return $return;
} else {
// Throw error if method not found
throw new BadMethodCallException("No such method exists: $name");
}
return new self;
}
public static function __callStatic($method, $parameters){
return (new self)->__callMethod($method, $parameters);
}
public function __call($method, $parameters){
return (new self)->__callMethod($method, $parameters);
}
}
All i need to do is return new self which does the trick instead of return $this so that the trailing function can be called easily.
Now i can able to call that function like this
Dropdown::cities()->toArray();
Reference:
https://stackoverflow.com/a/41631711/1156493
Thank you #Joseph for your time & support.

Laravel Controller use same object in two functions

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

Dependency Injection Container PHP

I've recently learned about the advantages of using Dependency Injection (DI) in my PHP application.
However, I'm still unsure how to create my container for the dependencies. Before, I use a container from a framework and I want to understand how is he doing things in back and reproduce it.
For example:
The container from Zend 2. I understand that the container make class dynamic, he does not have to know about them from the beginning, he checks if he already has that class in his registry and if he has not he check if that class exist and what parameters has inside constructor and put it in his own registry so next time could take it from there, practical is doing everything dynamic and it is completing his own registry, so we do not have to take care of nothing once we implement the container as he can give as any class we want even if we just make that class.
Also if I want to getInstance for A which needs B and B needs C I understand that he doing this recursive and he goes and instantiate C then B and finally A.
So I understand the big picture and what is he suppose to do but I am not so sure about how to implement it.
You may be better off using one of the existing Dependency Containers out there, such as PHP-DI or Pimple. However, if you are looking for a simpler solution, then I've implemented a Dependency Container as part of an article that I wrote here: http://software-architecture-php.blogspot.com/
Here is the code for the container
class Container implements \DecoupledApp\Interfaces\Container\ContainerInterface
{
/**
* This function resolves the constructor arguments and creates an object
* #param string $dataType
* #return mixed An object
*/
private function createObject($dataType)
{
if(!class_exists($dataType)) {
throw new \Exception("$dataType class does not exist");
}
$reflectionClass = new \ReflectionClass($dataType);
$constructor = $reflectionClass->getConstructor();
$args = null;
$obj = null;
if($constructor !== null)
{
$block = new \phpDocumentor\Reflection\DocBlock($constructor);
$tags = $block->getTagsByName("param");
if(count($tags) > 0)
{
$args = array();
}
foreach($tags as $tag)
{
//resolve constructor parameters
$args[] = $this->resolve($tag->getType());
}
}
if($args !== null)
{
$obj = $reflectionClass->newInstanceArgs($args);
}
else
{
$obj = $reflectionClass->newInstanceArgs();
}
return $obj;
}
/**
* Resolves the properities that have a type that is registered with the Container.
* #param mixed $obj
*/
private function resolveProperties(&$obj)
{
$reflectionClass = new \ReflectionClass(get_class($obj));
$props = $reflectionClass->getProperties();
foreach($props as $prop)
{
$block = new \phpDocumentor\Reflection\DocBlock($prop);
//This assumes that there is only one "var" tag.
//If there are more than one, then only the first one will be considered.
$tags = $block->getTagsByName("var");
if(isset($tags[0]))
{
$value = $this->resolve($tags[0]->getType());
if($value !== null)
{
if($prop->isPublic()) {
$prop->setValue($obj, $value);
} else {
$setter = "set".ucfirst($prop->name);
if($reflectionClass->hasMethod($setter)) {
$rmeth = $reflectionClass->getMethod($setter);
if($rmeth->isPublic()){
$rmeth->invoke($obj, $value);
}
}
}
}
}
}
}
/**
*
* #param string $dataType
* #return object|NULL If the $dataType is registered, the this function creates the corresponding object and returns it;
* otherwise, this function returns null
*/
public function resolve($dataType)
{
$dataType = trim($dataType, "\\");
$obj = null;
if(isset($this->singletonRegistry[$dataType]))
{
//TODO: check if the class exists
$className = $this->singletonRegistry[$dataType];
$obj = $className::getInstance();
}
else if(isset($this->closureRegistry[$dataType]))
{
$obj = $this->closureRegistry[$dataType]();
}
else if(isset($this->typeRegistry[$dataType]))
{
$obj = $this->createObject($this->typeRegistry[$dataType]);
}
if($obj !== null)
{
//Now we need to resolve the object properties
$this->resolveProperties($obj);
}
return $obj;
}
/**
* #see \DecoupledApp\Interfaces\Container\ContainerInterface::make()
*/
public function make($dataType)
{
$obj = $this->createObject($dataType);
$this->resolveProperties($obj);
return $obj;
}
/**
*
* #param Array $singletonRegistry
* #param Array $typeRegistry
* #param Array $closureRegistry
*/
public function __construct($singletonRegistry, $typeRegistry, $closureRegistry)
{
$this->singletonRegistry = $singletonRegistry;
$this->typeRegistry = $typeRegistry;
$this->closureRegistry = $closureRegistry;
}
/**
* An array that stores the mappings of an interface to a concrete singleton class.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* #var Array
*/
private $singletonRegistry;
/**
* An array that stores the mappings of an interface to a concrete class.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* #var Array
*/
private $typeRegistry;
/**
* An array that stores the mappings of an interface to a closure that is used to create and return the concrete object.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* #var Array
*/
private $closureRegistry;
}
The above code can be found here: https://github.com/abdulla16/decoupled-app (under the /Container folder)
You can register your dependencies as a singleton, as a type (every time a new object will be instantiated), or as a closure (the container will call the function that you register and that function is expected to return the instance).
For example,
$singletonRegistry = array();
$singletonRegistry["DecoupledApp\\Interfaces\\UnitOfWork\\UnitOfWorkInterface"] =
"\\DecoupledApp\\UnitOfWork\\UnitOfWork";
$typeRegistry = array();
$typeRegistry["DecoupledApp\\Interfaces\\DataModel\\Entities\\UserInterface"] =
"\\DecoupledApp\\DataModel\\Entities\\User";
$closureRegistry = array();
$closureRegistry["DecoupledApp\\Interfaces\\DataModel\\Repositories\\UserRepositoryInterface"] =
function() {
global $entityManager;
return $entityManager->getRepository("\\DecoupledApp\\DataModel\\Entities\\User");
};
$container = new \DecoupledApp\Container\Container($singletonRegistry, $typeRegistry, $closureRegistry);
This Container resolves properties of a class as well as the constructor parameters.
I have done a very simple IoC class which works as intended. I've investigated the IoC and DI pattern and especially after reading this answer. Let me know if something is not right or you have any questions .
<?php
class Dependency {
protected $object = null;
protected $blueprint = null;
/**
* #param $instance callable The callable passed to the IoC object.
*/
public function __construct($instance) {
if (!is_object($instance)) {
throw new InvalidArgumentException("Received argument should be object.");
}
$this->blueprint = $instance;
}
/**
* (Magic function)
*
* This function serves as man-in-the-middle for method calls,
* the if statement there serves for lazy loading the objects
* (They get created whenever you call the first method and
* all later calls use the same instance).
*
* This could allow laziest possible object definitions, like
* adding annotation parsing functionality which can extract everything during
* the call to the method. once the object is created it can get the annotations
* for the method, automatically resolve its dependencies and satisfy them,
* if possible or throw an error.
*
* all arguments passed to the method get passed to the method
* of the actual code dependency.
*
* #param $name string The method name to invoke
* #param $args array The array of arguments which will be passed
* to the call of the method
*
* #return mixed the result of the called method.
*/
public function __call($name, $args = array())
{
if (is_null($this->object)) {
$this->object = call_user_func($this->blueprint);
}
return call_user_func_array(array($this->object, $name), $args);
}
}
/*
* If the object implements \ArrayAccess you could
* have easier access to the dependencies.
*
*/
class IoC {
protected $immutable = array(); // Holds aliases for write-protected definitions
protected $container = array(); // Holds all the definitions
/**
* #param $alias string Alias to access the definition
* #param $callback callable The calback which constructs the dependency
* #param $immutable boolean Can the definition be overriden?
*/
public function register ($alias, $callback, $immutable = false) {
if (in_array($alias, $this->immutable)) {
return false;
}
if ($immutable) {
$this->immutable[] = $alias;
}
$this->container[$alias] = new Dependency($callback);
return $this;
}
public function get ($alias) {
if (!array_key_exists($alias, $this->container)) {
return null;
}
return $this->container[$alias];
}
}
class FooBar {
public function say()
{
return 'I say: ';
}
public function hello()
{
return 'Hello';
}
public function world()
{
return ', World!';
}
}
class Baz {
protected $argument;
public function __construct($argument)
{
$this->argument = $argument;
}
public function working()
{
return $this->argument->say() . 'Yep!';
}
}
/**
* Define dependencies
*/
$dic = new IoC;
$dic->register('greeter', function () {
return new FooBar();
});
$dic->register('status', function () use ($dic) {
return new Baz($dic->get('greeter'));
});
/**
* Real Usage
*/
$greeter = $dic->get('greeter');
print $greeter->say() . ' ' . $greeter->hello() . ' ' . $greeter->world() . PHP_EOL . '<br />';
$status = $dic->get('status');
print $status->working();
?>
I think the code is pretty self-explanatory, but let me know if something is not clear
Because I haven't find anything near what I wanted,I tried to implement on my own a container and I want to hear some opinion about how is looking,because I've start to learn php and oop a month ago a feedback is very important for me because I know I have many things to learn,so please feel free to bully my code :))
<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<?php
class ioc
{
private $defs;
static $instance;
private $reflection;
private function __construct()
{
$defs = array();
$reflection = array();
}
private function __clone()
{
;
}
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new ioc();
}
return self::$instance;
}
public function getInstanceOf($class)
{
if (is_array($this->defs) && key_exists($class, $this->defs)) {
if (is_object($this->defs[$class])) {
return $this->defs[$class];
}
} else {
if (class_exists($class)) {
if (is_array($this->reflection) && key_exists($class, $this->reflection)) {
$reflection = $this->reflection[$class];
} else {
$reflection = new ReflectionClass($class);
$this->reflection[$class] = $reflection;
}
$constructor = $reflection->getConstructor();
if ($constructor) {
$params = $constructor->getParameters();
if ($params) {
foreach ($params as $param) {
$obj[] = $this->getInstanceOf($param->getName());
}
$class_instance = $reflection->newInstanceArgs($obj);
$this->register($class, $class_instance);
return $class_instance;
}
}
if (!$constructor || !$params) {
$class_instance = new $class;
$this->register($class, $class_instance);
return $class_instance;
}
}
}
}
public function register($key, $class)
{
$this->defs[$key] = $class;
}
}
?>

Creating customer specific routes in Zend Framework 2

I'm developing a WebApp which (as usual) must support customer specific functionalities.
To achieve it I plan to set the customer name in the local app configuration (config/autoload/local.php )
configuration file so that I can use it to call the specialized code later on.
The module folder structure is this:
/module/Application
/module/Application/config
/module/Application/src
/module/Application/src/Application
/module/Application/src/Application/Controller
/module/Application/src/Application/Controller/[customer_instance_name]
/module/Application/src/Application/Model
/module/Application/src/Application/Model/[customer_instance_name]
/module/Application/view
/module/Application/view/Application
/module/Application/view/Application/[action]
/module/Application/view/Application/[action]/[customer_instance_name]
Using a custom ViewModel I inject the specific customer name to the template path:
namespace Application\Model;
use Zend\View\Model\ViewModel;
use Zend\View\Resolver\TemplatePathStack;
use Zend\Mvc\Service\ViewTemplatePathStackFactory;
class MyViewModel extends ViewModel
{
private $customInstanceName;
private $pathStack;
/**
* Constructor
*
* #param null|array|Traversable $variables
* #param array|Traversable $options
*/
public function __construct($variables = null, $options = null)
{
parent::__construct ( $variables, $options );
$serviceLocator = MySingleton::instance()->serviceLocator;
$factory = new ViewTemplatePathStackFactory();
$this->pathStack = $factory->createService($serviceLocator);
$config = $serviceLocator->get('config');
if (isset($config['custom_instance_name']) AND ($config['custom_instance_name']!='')) {
$this->customInstanceName = $config['custom_instance_name'];
} else {
$this->customInstanceName = false;
}
}
/**
* Set the template to be used by this model
*
* #param string $template
* #return ViewModel
*/
public function setTemplate($template)
{
$this->template = (string) $template;
if ( $this->customInstanceName === false) {
return $this;
}
$pathComponents = explode('/', (string) $template);
$last = array_pop($pathComponents);
array_push($pathComponents, $this->customInstanceName);
array_push($pathComponents, $last);
$customTemplate = implode('/', $pathComponents);
if ($this->pathStack->resolve($customTemplate) !== false) {
$this->template = $customTemplate;
}
return $this;
}
}
Using the "Decorator Pattern" I can achieve the same customization level on my Models.
I'm having problem to handle specific behavior. In this case I plan to create custom Controllers extending
my base controller class, but I'unable to call those controllers since the routing is defined on the module
config (and I was unable to change it in runtime).
My questions are:
1) Is this approach correct, or there is a better way to do it?
2) If the approach is correct, how can I define a custom router to be used when the ServiceManager reads my routing config?
Just found a solution. Will register it here hoping someone will benefit from it.
All I had to do was to create a specific router class with a match method which returns the correct routing target for each customer controller, and add it to my module.config.php as the type for each action.
namespace TARGETNAMESPACE;
use Traversable;
use Zend\Mvc\Router\Exception;
use Zend\Mvc\Router\Http\RouteInterface;
use Zend\Mvc\Router\Http\RouteMatch;
use Zend\Mvc\Router\Http\Literal;
use Zend\Stdlib\ArrayUtils;
use Zend\Stdlib\RequestInterface as Request;
class MyRouterLiteral extends Literal {
public function match(Request $request, $pathOffset = null) {
if (! method_exists($request, 'getUri')) {
return null;
}
$uri = $request->getUri();
$path = $uri->getPath();
if ($pathOffset !== null) {
if ($pathOffset >= 0 && strlen($path) >= $pathOffset && ! empty($this->route)) {
if (strpos($path, $this->route, $pathOffset) === $pathOffset) {
return new RouteMatch($this->getDefaults(), strlen($this->route));
}
}
return null;
}
if ($path === $this->route) {
return new RouteMatch($this->getDefaults(), strlen($this->route));
}
return null;
}
private function getDefaults() {
$aux = explode('\\', $this->defaults['controller']);
$last = array_pop($aux);
array_push($aux, '[CUSTOM_INSTANCE_NAME]');
array_push($aux, '[CUSTOM_INSTANCE_NAME]'.$last);
$result = $this->defaults;
$result['controller'] = implode('\\', $aux);
return $result;
}
}
To address all cases I had to create a second custom router (for segment routes) which follows the same rules and can be easily derived from the above code.

How to Cast Objects in PHP

Ive some classes that share some attributes, and i would like to do something like:
$dog = (Dog) $cat;
is it possible or is there any generic work around?
Its not a superclass, or a interface or related in any way. They are just 2 different classes i would like php map the attributes from a cat class to a dog and give me the new object. –
i guess i have to specify a little bit more cause seem like a senseless thing to do.
i've classes that inherits from different parent classes cause i've made an inheritance tree based on the saving method, maybe my bad from the beginning, but the problem is that i have a lot of classes that are practically equal but interacts one with mysql and the other one with xml files. so i have:
class MySql_SomeEntity extends SomeMysqlInteract{}
and
Xml_SomeEntity extends SomeXmlInteract{}
its a little bit deeper tree but the problem its that. i cant make them inherits from the same class cause multiple inheritance is not allowed, and i cant separate current interaction with superclases cause would be a big trouble.
Basically the attributes in each one are practical the same.
since i have a lot of this matching classes i would like to do some generic casting or something like it that can converts (pass the values to each attribute) and but im trying to search the simplest way to everyone of this classes.
You can use above function for casting not similar class objects (PHP >= 5.3)
/**
* Class casting
*
* #param string|object $destination
* #param object $sourceObject
* #return object
*/
function cast($destination, $sourceObject)
{
if (is_string($destination)) {
$destination = new $destination();
}
$sourceReflection = new ReflectionObject($sourceObject);
$destinationReflection = new ReflectionObject($destination);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$sourceProperty->setAccessible(true);
$name = $sourceProperty->getName();
$value = $sourceProperty->getValue($sourceObject);
if ($destinationReflection->hasProperty($name)) {
$propDest = $destinationReflection->getProperty($name);
$propDest->setAccessible(true);
$propDest->setValue($destination,$value);
} else {
$destination->$name = $value;
}
}
return $destination;
}
EXAMPLE:
class A
{
private $_x;
}
class B
{
public $_x;
}
$a = new A();
$b = new B();
$x = cast('A',$b);
$x = cast('B',$a);
There is no built-in method for type casting of user defined objects in PHP. That said, here are several possible solutions:
1) Use a function like the one below to deserialize the object, alter the string so that the properties you need are included in the new object once it's deserialized.
function cast($obj, $to_class) {
if(class_exists($to_class)) {
$obj_in = serialize($obj);
$obj_out = 'O:' . strlen($to_class) . ':"' . $to_class . '":' . substr($obj_in, $obj_in[2] + 7);
return unserialize($obj_out);
}
else
return false;
}
2) Alternatively, you could copy the object's properties using reflection / manually iterating through them all or using get_object_vars().
This article should enlighten you on the "dark corners of PHP" and implementing typecasting on the user level.
Without using inheritance (as mentioned by author), it seems like you are looking for a solution that can transform one class to another with preassumption of the developer knows and understand the similarity of 2 classes.
There's no existing solution for transforming between objects. What you can try out are:
get_object_vars() : convert object to array
Cast to Object: convert array to object
You do not need casting. Everything is dynamic.
I have a class Discount.
I have several classes that extends this class:
ProductDiscount
StoreDiscount
ShippingDiscount
...
Somewhere in the code I have:
$pd = new ProductDiscount();
$pd->setDiscount(5, ProductDiscount::PRODUCT_DISCOUNT_PERCENT);
$pd->setProductId(1);
$this->discounts[] = $pd;
.....
$sd = new StoreDiscount();
$sd->setDiscount(5, StoreDiscount::STORE_DISCOUNT_PERCENT);
$sd->setStoreId(1);
$this->discounts[] = $sd;
And somewhere I have:
foreach ($this->discounts as $discount){
if ($discount->getDiscountType()==Discount::DISCOUNT_TYPE_PRODUCT){
$productDiscount = $discount; // you do not need casting.
$amount = $productDiscount->getDiscountAmount($this->getItemTotalPrice());
...
}
}// foreach
Where getDiscountAmount is ProductDiscount specific function, and getDiscountType is Discount specific function.
a better aproach:
class Animal
{
private $_name = null;
public function __construct($name = null)
{
$this->_name = $name;
}
/**
* casts object
* #param Animal $to
* #return Animal
*/
public function cast($to)
{
if ($to instanceof Animal) {
$to->_name = $this->_name;
} else {
throw(new Exception('cant cast ' . get_class($this) . ' to ' . get_class($to)));
return $to;
}
public function getName()
{
return $this->_name;
}
}
class Cat extends Animal
{
private $_preferedKindOfFish = null;
public function __construct($name = null, $preferedKindOfFish = null)
{
parent::__construct($name);
$this->_preferedKindOfFish = $preferedKindOfFish;
}
/**
* casts object
* #param Animal $to
* #return Animal
*/
public function cast($to)
{
parent::cast($to);
if ($to instanceof Cat) {
$to->_preferedKindOfFish = $this->_preferedKindOfFish;
}
return $to;
}
public function getPreferedKindOfFish()
{
return $this->_preferedKindOfFish;
}
}
class Dog extends Animal
{
private $_preferedKindOfCat = null;
public function __construct($name = null, $preferedKindOfCat = null)
{
parent::__construct($name);
$this->_preferedKindOfCat = $preferedKindOfCat;
}
/**
* casts object
* #param Animal $to
* #return Animal
*/
public function cast($to)
{
parent::cast($to);
if ($to instanceof Dog) {
$to->_preferedKindOfCat = $this->_preferedKindOfCat;
}
return $to;
}
public function getPreferedKindOfCat()
{
return $this->_preferedKindOfCat;
}
}
$dogs = array(
new Dog('snoopy', 'vegetarian'),
new Dog('coyote', 'any'),
);
foreach ($dogs as $dog) {
$cat = $dog->cast(new Cat());
echo get_class($cat) . ' - ' . $cat->getName() . "\n";
}
It sounds like what you really want to do is implement an interface.
Your interface will specify the methods that the object can handle and when you pass an object that implements the interface to a method that wants an object that supports the interface, you just type the argument with the name of the interface.
You may think about factories
class XyFactory {
public function createXyObject ($other) {
$new = new XyObject($other->someValue);
// Do other things, that let $new look like $other (except the used class)
return $new;
}
}
Otherwise user250120s solution is the only one, which comes close to class casting.
class It {
public $a = '';
public function __construct($a) {
$this->a = $a;
}
public function printIt() {
;
}
}
//contains static function to 'convert' instance of parent It to sub-class instance of Thing
class Thing extends it {
public $b = '';
public function __construct($a, $b) {
$this->a = $a;
$this->b = $b;
}
public function printThing() {
echo $this->a . $this->b;
}
//static function housed by target class since trying to create an instance of Thing
static function thingFromIt(It $it, $b) {
return new Thing($it->a, $b);
}
}
//create an instance of It
$it = new It('1');
//create an instance of Thing
$thing = Thing::thingFromIt($it, '2');
echo 'Class for $it: ' . get_class($it);
echo 'Class for $thing: ' . get_class($thing);
Returns:
Class for $it: It
Class for $thing: Thing
I think that the best approach is to just create a new instance of a class and than assign the object. Here's what I would do:
public function ($someVO) {
$someCastVO = new SomeVO();
$someCastVO = $someVO;
$someCastVO->SomePropertyInVO = "123";
}
Doing this will give you code hinting in most IDEs and help ensure you are using the correct properties.
If the object you are trying to cast from or to has properties that are also user-defined classes, and you don't want to go through reflection, you can use this.
<?php
declare(strict_types=1);
namespace Your\Namespace\Here
{
use Zend\Logger; // or your logging mechanism of choice
final class OopFunctions
{
/**
* #param object $from
* #param object $to
* #param Logger $logger
*
* #return object
*/
static function Cast($from, $to, $logger)
{
$logger->debug($from);
$fromSerialized = serialize($from);
$fromName = get_class($from);
$toName = get_class($to);
$toSerialized = str_replace($fromName, $toName, $fromSerialized);
$toSerialized = preg_replace("/O:\d*:\"([^\"]*)/", "O:" . strlen($toName) . ":\"$1", $toSerialized);
$toSerialized = preg_replace_callback(
"/s:\d*:\"[^\"]*\"/",
function ($matches)
{
$arr = explode(":", $matches[0]);
$arr[1] = mb_strlen($arr[2]) - 2;
return implode(":", $arr);
},
$toSerialized
);
$to = unserialize($toSerialized);
$logger->debug($to);
return $to;
}
}
}
You can opt for this example below. Hope it will help.
/** #var ClassName $object */
$object->whateverMethod() // any method defined in the class can be accessed by $object
I know this is not a cast but it can be useful sometimes.
PHP provides a very simple way of doing this by using:
(object) ['id'=>1,'name'=>'cat']
https://www.php.net/manual/en/language.types.object.php
In your case you try this:
$dog = json_encode($dog);
$cat = (object) json_decode($dog)
More optimize method is:
$dog = (array)$dog;
$dog['newfield'] = 'xyz';
$dog = (object)$dog;

Categories