Since my last question related to this I have managed to create a working base and to understand how MVC works.
I'm writing RESTful APIS in PHP, they serve the purpose but I see that my code is repeating.
For example, for each action, I have a controller, a service... etc and a lot of that code can be reused not that I write a ton of code for one simple route.
I have tried a few of my ideas but I end up having spaghetti code and it does not look clean.
Here is folder the structure in one of my APIS.
.
├── README.md
├── apache_default
├── composer.json
├── composer.lock
├── config
│ ├── config-development.yml
│ ├── config-production.yml
│ ├── dependencies
│ │ ├── common
│ │ │ ├── cashing.yml
│ │ │ ├── components.yml
│ │ │ ├── controllers.yml
│ │ │ ├── domains.yml
│ │ │ ├── middleware.yml
│ │ │ ├── objmap.yml
│ │ │ ├── repositories.yml
│ │ │ └── services.yml
│ │ ├── development
│ │ │ └── db.yml
│ │ ├── general-production.yml
│ │ ├── general.yml
│ │ └── main.yml
│ ├── parameters
│ │ └── development
│ │ └── tables.yml
│ └── routing.yml
├── phpunit.xml
├── public
│ ├── Bootstrap.php
│ ├── Kernel.php
│ ├── index.php
│ └── monolog.log
├── resources
│ ├── git
│ │ ├── diagram.png
│ │ ├── schema.png
│ │ └── schema_1.png
│ └── loggs
│ └── monolog.txt
├── src
│ └── Spartan
│ ├── Core
│ │ ├── Component
│ │ │ ├── Collection.php
│ │ │ ├── Controller.php
│ │ │ ├── DataMapper.php
│ │ │ ├── Exception.php
│ │ │ ├── MapperFactory.php
│ │ │ └── Service.php
│ │ ├── Database
│ │ │ ├── ES.php
│ │ │ └── PDOCompat.php
│ │ ├── Entities
│ │ │ ├── CanPersistMapper.php
│ │ │ ├── HasId.php
│ │ │ └── ResponseBootstrap.php
│ │ ├── Logger
│ │ │ └── Logger.php
│ │ └── Mapper
│ │ └── CanCreateMapper.php
│ ├── Models
│ │ ├── Adapters
│ │ ├── Cashing
│ │ │ └── WorkoutCashing.php
│ │ ├── Collections
│ │ │ ├── DescriptionCollection.php
│ │ │ ├── ExerciseCollection.php
│ │ │ ├── NameCollection.php
│ │ │ ├── RoundCollection.php
│ │ │ ├── TagCollection.php
│ │ │ ├── WorkoutCollection.php
│ │ │ └── WorkoutListCollection.php
│ │ ├── Domains
│ │ │ ├── AddWorkoutDomain
│ │ │ │ └── AddWorkoutDomain.php
│ │ │ ├── DeleteWorkoutDomain
│ │ │ │ └── DeleteWorkoutDomain.php
│ │ │ ├── EditWorkoutDomain
│ │ │ │ └── EditWorkoutDomain.php
│ │ │ ├── GetWorkoutDomain
│ │ │ │ └── GetWorkoutDomain.php
│ │ │ ├── GetWorkoutIdsDomain
│ │ │ │ └── GetWorkoutIdsDomain.php
│ │ │ ├── GetWorkoutListDomain
│ │ │ │ └── GetWorkoutListDomain.php
│ │ │ ├── ReleaseWorkoutDomain
│ │ │ │ └── ReleaseWorkoutDomain.php
│ │ │ └── WorkoutExsistsDomain
│ │ │ └── WorkoutExsistsDomain.php
│ │ ├── Entities
│ │ │ ├── Description.php
│ │ │ ├── Exercise.php
│ │ │ ├── Name.php
│ │ │ ├── Round.php
│ │ │ ├── Tag.php
│ │ │ ├── Version.php
│ │ │ ├── Workout.php
│ │ │ └── WorkoutList.php
│ │ ├── Exceptions
│ │ │ ├── BaseError.php
│ │ │ ├── DescriptionConfliect.php
│ │ │ ├── ESError.php
│ │ │ ├── NameConflict.php
│ │ │ ├── RoundError.php
│ │ │ └── TagError.php
│ │ ├── Facades
│ │ ├── Helpers
│ │ ├── Interfaces
│ │ │ └── CanPersistWorkout.php
│ │ ├── Mappers
│ │ │ ├── VersionMapper.php
│ │ │ ├── WorkoutBase.php
│ │ │ ├── WorkoutDescription.php
│ │ │ ├── WorkoutListMapper.php
│ │ │ ├── WorkoutName.php
│ │ │ ├── WorkoutRound.php
│ │ │ └── WorkoutTag.php
│ │ ├── Middlewares
│ │ │ ├── CreateWorkoutMiddleware.php
│ │ │ ├── DeleteWorkoutMiddleware.php
│ │ │ ├── EditWorkoutMiddleware.php
│ │ │ ├── GetWorkoutByIdsMiddleware.php
│ │ │ ├── GetWorkoutListMiddleware.php
│ │ │ ├── GetWorkoutMiddleware.php
│ │ │ └── ReleaseWorkoutMiddleware.php
│ │ ├── Repositories
│ │ │ └── WorkoutRepository.php
│ │ └── Services
│ │ └── WorkoutService.php
│ └── Presentation
│ ├── Controller
│ │ ├── LogController.php
│ │ └── WorkoutController.php
│ └── Mappers
│ └── WorkoutMapp.php
└── tests
├── bootstrap.php
├── fixture
├── integration
├── mock
│ ├── Entity.php
│ ├── Factory.php
│ ├── Mapper.php
│ ├── RepoEntity.php
│ └── RepoMapper.php
├── report
└── unit
└── Spartan
├── Cashing
│ └── WorkoutCashingTest.php
├── Component
│ ├── CollectionTest.php
│ ├── DataMapperTest.php
│ └── MapperFactoryTest.php
├── Controller
│ └── MappTest.php
├── Domains
├── Entities
│ ├── DescriptionTest.php
│ ├── ExerciseTest.php
│ ├── NameTest.php
│ ├── RoundTest.php
│ ├── TagTest.php
│ ├── VersionTest.php
│ ├── WorkoutListTest.php
│ └── WorkoutTest.php
├── Mappers
│ ├── VersionTest.php
│ ├── WorkoutBaseTest.php
│ ├── WorkoutDescriptionTest.php
│ ├── WorkoutListTest.php
│ ├── WorkoutNameTest.php
│ ├── WorkoutRoundTest.php
│ └── WorkoutTagTest.php
├── Repositories
│ └── WorkoutTest.php
└── Services
└── WorkoutTest.php
For each activity I have a controller, that controller has a service injected into it via DI over yml.
When the service loads I have a domain folder where I keep my logic and over a factory, I create mappers there which I need(if I need them).
Each domain does one action, for example version workout(I need this for auditing of my data in MySQL).
Than I have a middleware in my service which does cashing(I'm working to change)
And thats basically how each of my routes works a lot of repeated code.
It looks very boring to write code now and I need a push into another direction.
Here is a part of my code:
index.php
<?php
//Display errors
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
use Symfony\Component\HttpFoundation\Request;
// load vendor
require __DIR__.'/../vendor/autoload.php';
require_once __DIR__.'/../public/Kernel.php';
require_once __DIR__.'/../public/Bootstrap.php';
// new kernel
$kernel = new Kernel('dev');
$bootstrap = new Bootstrap;
// new request
$request = Request::createFromGlobals();
// loader interface
$config = $kernel->registerContainerConfiguration();
// response from
$response = $bootstrap->handle($request,$config,null);
bootstrap.php
<?php
class Bootstrap
{
public function handle($request,$config,$ipRange)
{
// Configuration
$locator = new FileLocator(__DIR__ . '/../config');
$data = new ResponseBootstrap();
// Create a log channel
$log = new Logger('spartan_workouts_ms');
$log->pushHandler(new StreamHandler('monolog.log'));
$log->pushHandler(new LogglyHandler('55920048-11f0-4b7e-a203-e90083d6962d/tag/monolog', Logger::INFO));
// Dependency Injection Container
$container = new DependencyInjection\ContainerBuilder;
$loader = new DependencyInjection\Loader\YamlFileLoader($container, $locator);
$loader->load($config);
$container->compile();
// Routing
$loader = new Routing\Loader\YamlFileLoader($locator);
$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher(
$loader->load('routing.yml'),
$context
);
try{
$parameters = $matcher->match($request->getPathInfo());
foreach ($parameters as $key => $value) {
$request->attributes->set($key, $value);
}
$command = $request->getMethod() . $request->get('action');
$resource = "controller.{$request->get('controller')}";
$controller = $container->get($resource);
$data = $controller->{$command}($request);
}
// log custom thrown exceptions
catch (\Exception $e) {
/**
* This is to slow it takes to much time to log
*/
// // log errors
// $log->addWarning(
// json_encode([
// "date"=> date("Y-m-d h:i:s"),
// "code"=> $e->getCode(),
// "message"=> $e->getMessage(),
// "file"=> $e->getFile(),
// "line"=> $e->getLine()
// ])
// );
$data->setData([
"date"=> date("Y-m-d h:i:s"),
"code"=> $e->getCode(),
"message"=> $e->getMessage(),
"file"=> $e->getFile(),
"line"=> $e->getLine()
]);
if($e->getCode() == 0){
// TODO log data
$data->setStatus(404);
}else{
// TODO log data
$data->setStatus($e->getCode());
}
$data->setMessage($e->getMessage());
//echo "log to monolog";
} catch (\TypeError $error) {
// TODO log data
$data->setStatus(404);
$data->setMessage(new Response("Invalid dependency: {$error->getMessage()}"));
die(print_r(new Response("Invalid dependency: {$error->getMessage()}")));
}
// Check if json in array from
if(!empty($data->getData())){
$response = new JsonResponse($data->getData());
}else{
// Not json
$response = new Response;
}
//Set custom headers
$response->setStatusCode(
(int)$data->getStatus(),
empty($data->getMessage()) ? $data->getMessage() : null
);
// preflighted request handle
if($request->getMethod() === 'OPTIONS'){
// set status
$response->setStatusCode((int)200);
}
// headers
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET,HEAD,OPTIONS,POST,PUT,DELETE');
$response->headers->set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
$response->headers->set('Access-Control-Allow-Credentials', 'true');
$response->headers->set("Access-Control-Max-Age", "1728000");
// return response
$response->send();
return $response;
}
}
Kernel.php
<?php
class Kernel
{
protected $env;
public function __construct($env = null)
{
if (is_null($env)) {
$this->env = is_null($env) ? 'prod' : 'dev';
}else{
$this->env = $env;
}
}
/**
* Loads the container configuration.
*/
public function registerContainerConfiguration()
{
if((string)$this->env === (string)'dev'){
$configuration = 'config-development.yml';
}else{
$configuration = 'config-production.yml';
}
return $configuration;
}
}
WorkoutController.php
<?php
class WorkoutController extends Controller
{
private $workoutService;
private $workoutMapp;
public function __construct(WorkoutService $workoutService, WorkoutMapp $workoutMapp){
$this->workoutService = $workoutService;
$this->workoutMapp = $workoutMapp;
// construct the parent
parent::__construct();
}
/**
* Get workout
* Get workouts by id
* Get workout list
*
* @param Request $request
* @return ResponseBootstrap
*/
public function get(Request $request):ResponseBootstrap
{
$workout = $this->workoutMapp->getWorkoutUniversal($request);
// list
if(!is_null($workout->getOffset()) && !is_null($workout->getLimit()) && !is_null($workout->getState())){
return $this->workoutService->getWorkoutList($workout);
}
// get workout by id
if($workout->getId() && $workout->getState()){
return $this->workoutService->getWorkout($workout);
}
// get workout ids
if($workout->getIds()){
return $this->workoutService->getWorkoutIds($workout);
}
return $this->badRequest();
}
/**
* Create workout
*
* @param Request $request
* @return ResponseBootstrap
*/
public function post(Request $request)
{
// raw data
$data = json_decode($request->getContent(), true);
// map raw data to workout object
$workout = $this->workoutMapp->addWorkout($data);
// check if the name, description, tags and rounds are not empty
if(!empty($workout->getNames()->toArray()) && !empty($workout->getDescriptions()->toArray()) && !empty($workout->getTags()->toArray()) && !empty($workout->getRounds()->toArray())){
return $this->workoutService->addWorkout($workout);
}
// when empty return a response from the base controller
return $this->badRequest();
}
....
WorkoutService.php
<?php
class WorkoutService extends Service
{
......
/**
* Add Workout
*
* @param Workout $workout
* @return ResponseBootstrap
*/
public function addWorkout(Workout $workout):ResponseBootstrap
{
// middleware for handling cashing
$this->createWorkoutMiddleware->handle(
$this->addWorkoutDomain,
$workout,
$this->getWorkoutDomain);
return $this->formResponse($workout, true);
}
/**
* Delete Workout
*
* @param Workout $workout
* @return ResponseBootstrap
*/
public function deleteWorkout(Workout $workout):ResponseBootstrap
{
// middleware for handling cashing
$this->deleteWorkoutMiddleware->handle(
$this->deleteWorkoutDomain,
$workout);
return $this->formResponse($workout, false);
}
/**
* Edit Workout
*
* @param Workout $workout
* @return ResponseBootstrap
*/
public function editWorkout(Workout $workout):ResponseBootstrap
{
// middleware for handling cashing
$this->editWorkoutMiddleware->handle(
$this->editWorkoutDomain,
$workout,
$this->getWorkoutDomain);
return $this->formResponse($workout, false);
}
WorkoutRepository.php
<?php
class WorkoutRepository implements CanPersistWorkout
{
private $mapperFactory;
private $list = [
Workout::class => WorkoutBase::class,
Round::class => WorkoutRound::class,
Name::class => WorkoutName::class,
Version::class => VersionMapper::class,
Description::class => WorkoutDescription::class,
Tag::class => WorkoutTag::class,
RoundCollection::class => WorkoutRound::class,
NameCollection::class => WorkoutName::class,
DescriptionCollection::class => WorkoutDescription::class,
TagCollection::class => WorkoutTag::class,
WorkoutListCollection::class => WorkoutListMapper::class
];
public function __construct(CanCreateMapper $mapperFactory)
{
$this->mapperFactory = $mapperFactory;
}
/*********************************************************************************************/
/************************************ Store **************************************/
/*********************************************************************************************/
public function storeDescription(Description $description, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($description), $override);
$mapper->store($description);
}
public function storeBase(Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($workout), $override);
$mapper->store($workout);
}
public function storeRound(Round $round, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($round), $override);
$mapper->store($round);
}
public function storeName(Name $name, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($name), $override);
$mapper->store($name);
}
public function versionUp(Version $version, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($version), $override);
$mapper->versionUp($version);
}
public function storeTag(Tag $tag, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($tag), $override);
$mapper->store($tag);
}
/*********************************************************************************************/
/************************************ Delete **************************************/
/*********************************************************************************************/
public function deleteWorkout(Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($workout), $override);
$mapper->delete($workout);
}
public function deleteRounds(Round $round, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($round), $override);
$mapper->delete($round,$workout);
}
public function deleteDescriptions(Description $description, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($description), $override);
$mapper->delete($description,$workout);
}
public function deleteNames(Name $name, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($name), $override);
$mapper->delete($name,$workout);
}
public function deleteTags(Tag $tag, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($tag), $override);
$mapper->delete($tag,$workout);
}
/*********************************************************************************************/
/************************************ Fetch **************************************/
/*********************************************************************************************/
public function getDescriptions(DescriptionCollection $description, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($description), $override);
$mapper->fetch($description, $workout);
}
public function getRounds(RoundCollection $round, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($round), $override);
$mapper->fetch($round, $workout);
}
public function getNames(NameCollection $name, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($name), $override);
$mapper->fetch($name,$workout);
}
public function getWorkout(Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($workout), $override);
$mapper->fetch($workout);
}
public function getTags(TagCollection $tag, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($tag), $override);
$mapper->fetch($tag, $workout);
}
public function getWorkoutList(WorkoutListCollection $list, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($list), $override);
$mapper->fetch($list, $workout);
}
public function getTotalWorkotus(Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($workout), $override);
$mapper->total($workout);
}
/*********************************************************************************************/
/*********************************** Update ********************************************/
/*********************************************************************************************/
public function updateNameState(NameCollection $name, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($name), $override);
$mapper->update($name, $workout);
}
public function updateRoundState(RoundCollection $round, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($round), $override);
$mapper->update($round,$workout);
}
public function updateBaseState(Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($workout), $override);
$mapper->update($workout);
}
public function updateDescriptionState(DescriptionCollection $description, Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($description), $override);
$mapper->update($description,$workout);
}
/*********************************************************************************************/
/*********************************** Audit ********************************************/
/*********************************************************************************************/
public function auditRound(Round $round,Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($round), $override);
$mapper->storeToAudit($round, $workout);
}
public function auditDescription(Description $description,Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($description), $override);
$mapper->storeToAudit($description, $workout);
}
public function auditBase(Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($workout), $override);
$mapper->storeToAudit($workout);
}
public function auditName(Name $name,Workout $workout, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($name), $override);
$mapper->storeToAudit($name,$workout);
}
/*********************************************************************************************/
/*********************************** Helpers ********************************************/
/*********************************************************************************************/
public function begginTransaction()
{
$mapper = $this->mapperFactory->create(WorkoutBase::class);
$mapper->begginTransaction();
}
public function commitTransaction()
{
$mapper = $this->mapperFactory->create(WorkoutBase::class);
$mapper->commit();
}
private function computeKey(string $key, string $override = null): string
{
if ($override !== null) {
$key = $override;
}
if (array_key_exists($key, $this->list) === false) {
throw new \RuntimeException("No mapper for class '{$key}' has been defined!");
}
return $key;
}
private function retrieveMapper(string $name, string $override = null)
{
$key = $this->computeKey($name, $override);
$entry = $this->list[$key];
return $this->mapperFactory->create($entry);
}
public function define(string $entity, string $mapper)
{
if (class_exists($entity) === false) {
throw new \RuntimeException("Entity class '{$entity}' was not found!");
}
if (class_exists($mapper) === false) {
throw new \RuntimeException("Mapper class '{$mapper}' was not found!");
}
$this->list[$entity] = $mapper;
}
public function load($identity, string $override = null)
{
$mapper = $this->retrieveMapper(get_class($identity), $override);
$mapper->fetch($identity);
}
}
VersionMapper.php
<?php
class VersionMapper extends DataMapper
{
/**
* Version Up
*
* @param Version $version
*/
public function versionUp(Version $version) // TODO handle exceptions
{
$sql = "INSERT INTO version VALUES(null)";
$statement = $this->connection->prepare($sql);
$statement->execute();
$version->setVersion($this->connection->lastInsertId());
}
}
AddWorkoutDomain.php
<?php
class AddWorkoutDomain
{
private $repository;
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
/*********************************************************************************************/
/***************************** Visible functions to children ******************************/
/*********************************************************************************************/
/**
* Handle
*
* @param Workout $workout
* @return array
*/
public function handle(Workout $workout):array
{
// beggin transaction
$this->repository->begginTransaction();
$messages = [];
$messages = array_merge($messages, $this->storeBase($workout));
$messages = array_merge($messages, $this->storeNames($workout));
$messages = array_merge($messages, $this->storeDescriptions($workout));
$messages = array_merge($messages, $this->storeRounds($workout));
$messages = array_merge($messages, $this->storeTags($workout));
// commit transaction
$this->repository->commitTransaction();
return $messages;
}
/**
* Get Total
*
* @param Workout $workout
*/
public function getTotal(Workout $workout):void
{
$this->repository->getTotalWorkotus($workout);
}
/*********************************************************************************************/
/************************************* Executors ******************************************/
/*********************************************************************************************/
/**
* Store Base
*
* @param Workout $workout
* @return array
*/
private function storeBase(Workout $workout):array
{
// version up
$workout->setVersion($this->versionUp()->getVersion());
$workout->setState('P');
$this->repository->storeBase($workout);
return ['success'=>'Base'];
}
/**
* Store Names
*
* @param Workout $workout
* @return array
*/
private function storeNames(Workout $workout):array
{
foreach($workout->getNames()->toArray() as $name){
// set workout parent
$name->setParent($workout->getId());
$name->setState('P');
$this->repository->storeName($name);
}
return ['success'=>'Names'];
}
/**
* Store Descriptions
*
* @param Workout $workout
* @return array
*/
private function storeDescriptions(Workout $workout):array
{
foreach($workout->getDescriptions()->toArray() as $description){
// set workout parent
$description->setParent($workout->getId());
$description->setState('P');
$this->repository->storeDescription($description);
}
return ['success'=>'Descriptions'];
}
/**
* Store Rounds
*
* @param Workout $workout
* @return array
*/
private function storeRounds(Workout $workout):array
{
foreach($workout->getRounds()->toArray() as $round){
// set workout parent
$round->setParent($workout->getId());
$round->setState('P');
$this->repository->storeRound($round);
}
return ['success'=>'Rounds'];
}
/**
* Store Tags
*
* @param Workout $workout
* @return array
*/
private function storeTags(Workout $workout):array
{
foreach($workout->getTags()->toArray() as $tag){
// set workout parent
$tag->setParent($workout->getId());
$this->repository->storeTag($tag);
}
return ['success'=>'Rounds'];
}
/**
* Version Up
*
* @return Version
*/
private function versionUp():Version
{
$version = new Version();
$this->repository->versionUp($version);
return $version;
}
}
Any comment and advice are welcome, I'm seeking to expand my knowledge.
Credits to Tereško for helping me out.
2 Answers 2
Take the following Kernel
class:
class Kernel
{
protected $env;
const ENV_TYPE_DEV = 'dev';
const ENV_TYPE_PROD = 'prod';
public function __construct($env = null)
{
$this->env = $env ?? self::ENV_TYPE_DEV;
}
/**
* Loads the container configuration.
*/
public function registerContainerConfiguration()
{
if ($this->env === self::ENV_TYPE_DEV) {
$configuration = 'config-development.yml';
} else {
$configuration = 'config-production.yml';
}
return $configuration;
}
}
The changes I made are as follows:
- Defined two constants so you can reference them rather than passing a direct string as the
$env
. This also gives you the option to change the values in the future without having to consider hardcoded versions of the values - Simplified your
if
statement (via the null coalescing operator) in your__construct
to set the$env
property to whatever is passed toENV_TYPE_DEV
as a default - In
registerContainerConfiguration
I removed your casts as they were redundant and turned the hard-coded string into the respectiveconst
Then your index.php
file:
use Symfony\Component\HttpFoundation\Request;
// load vendor
require __DIR__.'/../vendor/autoload.php';
// autoloading done through composer
// error handling based on kernel env
// type that is passed.
// new kernel
$kernel = new Kernel(Kernel::ENV_TYPE_DEV);
$bootstrap = new Bootstrap;
// new request
$request = Request::createFromGlobals();
// loader interface
$config = $kernel->registerContainerConfiguration();
// response from
$response = $bootstrap->handle($request, $config, null);
The changes I made / suggest are as follows:
- You should have your error reporting work in conjunction with your environment that is defined in your
Kernel
- You should autoload your classes through composer
- I changed the hardcoded
dev
string to the defined constant
In your AddWorkoutDomain
class, I'd simplify the handle function:
/**
* Handle
*
* @param Workout $workout
* @return array
*/
public function handle(Workout $workout):array
{
// beggin transaction
$this->repository->begginTransaction();
$messages = array_merge($messages, $this->storeBase($workout), $this->storeNames($workout), $this->storeDescriptions($workout), $this->storeRounds($workout), $this->storeTags($workout));
// commit transaction
$this->repository->commitTransaction();
return $messages;
}
- Merged the
array_merge
calls into one (pun intended...), as per the documentation, the second parameter can be a list of variables to merge, doesn't have to be one per function call
Miscellaneous
- Opinionated but, you seem to have quite a few blank lines within your classes, I would reduce that down
In various places you check for
null
variables in long, drawn out, if statements:if(!is_null($workout->getOffset()) && !is_null($workout->getLimit()) && !is_null($workout->getState())){ return $this->workoutService->getWorkoutList($workout); }
Consider the following:
if (isset($workout->getOffset(), $workout->getLimit(), $workout->getState())) {
}
-
\$\begingroup\$ I have implemented your changes, thank you! I won't accept your answer but I will upvote it. If you could go through the whole code and give me full feedback on my code and classes and how it can be reduced I will accept it then. \$\endgroup\$DaAmidza– DaAmidza2019年04月03日 06:41:48 +00:00Commented Apr 3, 2019 at 6:41
Since nobody answered my question probably because I have not explained what I want here is an answer which satisfied my needs.
I have been wondering in the dark than I have found this answer which expanded my knowledge regarding programming.
I have seen some of those methods but I have never taught of the level of abstraction which they can bring.
As the accepted answer states:
Programming: Writing a program that creates, transforms, filters, aggregates and otherwise manipulates data.
Metaprogramming: Writing a program that creates, transforms, filters, aggregates and otherwise manipulates programs.
Generic Programming: Writing a program that creates, transforms, filters, aggregates and otherwise manipulates data, but makes only the minimum assumptions about the structure of the data, thus maximizing reuse across a wide range of data types.
I saw that I have been typing code which can be easily avoided.
Long story short here is my implementation to the issue I had.
My controller is still not there where I want it to be but I have managed to call my actions like this:
return $this->baseService->actionHandler($exercise,
[
BaseService::ACT_GET_NAME,
BaseService::ACT_GET_DESC,
BaseService::ACT_GET_BASE,
BaseService::ACT_GET_OBJ
],
$queryParams
);
And my service looks something like this:
class BaseService
{
// vars
private $domainRepository;
private $responseBootstrap;
private $versionDomain;
private $nameDomain;
private $tagDomain;
private $planDomain;
private $packageDomain;
private $roundDomain;
private $auditDomain;
private $deleteDomain;
private $languageDomain;
//actions create
const ACT_VERSION = 'versionUp';
const ACT_CREATE = 'createObject';
const ACT_SINGLE_OBJ = 'returnSingleObject';
const ACT_CRE_NAME = 'createName';
const ACT_CRE_DESC = 'createDescription';
const ACT_CRE_TAG = 'createTags';
const ACT_CRE_BODY = 'createBody';
const ACT_CRE_PKG_PLAN = 'createPackagePlan';
const ACT_CRE_WRK_PLN_DAY = 'createWorkoutPlanDay';
const ACT_CRE_ROUND = 'createRound';
const ACT_RELEASE = 'releaseContent';
const ACT_DELETE = 'delete';
// actions delete
const ACT_ED_NAME = 'editName';
const ACT_ED_OBJ = 'editObject';
const ACT_ED_DESC = 'editDescription';
const ACT_ED_TAG = 'editTag';
const ACT_ED_ROUND = 'editRound';
const ACT_ED_BODY = 'editBody';
const ACT_ED_DAY = 'editDay';
const ACT_ED_PKG_PLAN = 'editPackagePlan';
// actions get
const ACT_GET_NAME = 'getName';
const ACT_GET_DESC = 'getDescription';
const ACT_GET_BODY = 'getBody';
const ACT_GET_OBJ = 'getObjectResponse';
const ACT_GET_BASE = 'getBase';
// system actions
const SYS_ACT_BEG_TRANS = 'begginTransaction';
const SYS_ACT_COM_TRANS = 'commitTransaction';
const SYS_ACT_ROLB_TRANS = 'rollbackTransaction';
private $responseArray = [];
/**
* Constructor
*
* @param DomainRepository $domainRepository
* @param ResponseBootstrap $responseBootstrap
* @param VersionDomain $versionDomain
* @param NameDomain $nameDomain
* @param NameDomain $descriptionDomain
*/
public function __construct(
DomainRepository $domainRepository,
ResponseBootstrap $responseBootstrap,
VersionDomain $versionDomain,
NameDomain $nameDomain,
TagDomain $tagDomain,
PlanDomain $planDomain,
PackageDomain $packageDomain,
RoundDomain $roundDomain,
AuditDomain $auditDomain,
DeleteDomain $deleteDomain,
LanguageDomain $languageDomain)
{
$this->domainRepository = $domainRepository;
$this->responseBootstrap = $responseBootstrap;
$this->versionDomain = $versionDomain;
$this->nameDomain = $nameDomain;
$this->tagDomain = $tagDomain;
$this->planDomain = $planDomain;
$this->packageDomain = $packageDomain;
$this->roundDomain = $roundDomain;
$this->auditDomain = $auditDomain;
$this->deleteDomain = $deleteDomain;
$this->languageDomain = $languageDomain;
}
/**
* Action Handler
*
* @param object $object
* @param array $actions
* @return object
*/
public function actionHandler(object $object, array $actions = [], QueryParams $queryParams = null):object
{
try{
// beggin transaction
$this->domainRepository->begginTransaction();
foreach($actions as $action){
// on create return object
if($action == BaseService::ACT_SINGLE_OBJ){
// commit transaction
$this->domainRepository->commitTransaction();
return $this->{$action}($object);
}
// on get return object
else if($action == BaseService::ACT_GET_OBJ){
// commit transaction
$this->domainRepository->commitTransaction();
return $this->{$action}();
}else{
$this->{$action}($object, $queryParams);
}
}
}catch(\PDOException $e){
// handle rollback of sql action
$this->domainRepository->rollbackTransaction($e);
}
}
/*********************************************************************************************/
/*********************************** Executors *********************************/
/*********************************************************************************************/
/**
* Version Up
*
* @param object $object
*/
private function versionUp(object $object):void
{
// version up
$this->versionDomain->handle($object);
}
...........
The BaseService class has all the actions and it calls the DAO layer which is flexible enough to construct the query I need.
I need to add a caching layer to it and a few more things but this is the answer which I have been asking for(at least I think so if somebody else had a better version of it feel free to post your answer.