5
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

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 to ENV_TYPE_DEV as a default
  • In registerContainerConfiguration I removed your casts as they were redundant and turned the hard-coded string into the respective const

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())) {

}
\$\endgroup\$
  • \$\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 Apr 3 at 6:41
0
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.