2
\$\begingroup\$

The code below will store a base64 image on another website and create an event to display the image on a Vue page in real time. It works fine and all.

My question is, is this a good way of doing it or there's a better way to structure the code? I've thought about placing the logic in a listener but it doesn't seem that I need to use one. For queues, I'll add them later on.

Controller

public function store(Request $request)
{
    $base64 = $request->image;
    $image = str_replace('data:image/png;base64,', '', $base64);
    $imageName = 'draw'.time().'.png';
    $response = $this->guzzleClient()->post(config('archiving.api_postBase64_file'),
        [  
            'multipart' => [
                [
                    'name' => 'file',
                    'contents' => $image
                ],
                [
                    'name' => 'file_name',
                    'contents' => $imageName
                ],
                [
                    'name' => 'file_folder',
                    'contents' => 'drawings'
                ],
            ],
        ]
    );
    if($response->getStatusCode() >= 500){
        return response($response->getBody(), $response->getStatusCode());
    }else{
        $drawing = Drawing::create([
            'user_id'=>Auth::id(),
            'file_name'=>$imageName,
        ]);
        event(new NewDrawing($drawing));
        return response('Drawing created!',200);
    }
}

NewDrawing Event

class NewDrawing implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public $drawing;

    public function __construct(Drawing $drawing)
    {
        $this->drawing = $drawing;
    }
    public function broadcastOn()
    {
        return new Channel('channel-drawing');
    }
 }

Frontend (Vue)

<script>
export default {
    data(){
        return{
            images:'',
        }
    },
    methods:{
        listen() {
            Echo.channel('channel-drawing')
            .listen('NewDrawing', res => {
                this.images.push(res.drawing);
            })  
        }
    },
    mounted() {
        this.listen()
    }
}
</script>
\$\endgroup\$
2
  • \$\begingroup\$ Always OR Never use semis at end of statements. Either is fine but be consistent. You can always implement a strict linter to keep your code quality high. \$\endgroup\$
    – user229656
    Commented Aug 25, 2020 at 8:12
  • \$\begingroup\$ Your question title describes your concern, but it is expected to uniquely describe what your script does. \$\endgroup\$ Commented Aug 29, 2020 at 8:49

2 Answers 2

4
\$\begingroup\$

I'm mostly a back-end guy who fusses his way through front end code. Here's my advice:

  • Keep the controller code as close as possible to handling the response only. Delegate the creation of objects to their own classes. Think of the S in SOLID - Single Responsibility.

    public function store(Request $request)
    {
        $image = new ImageMeta($base64);
        $archiver = new ImageArchiver($image);
        $response = $archiver->post();
    
        if($response->getStatusCode() >= 500){
            return response($response->getBody(), $response->getStatusCode());
        }else{
            $drawing = Drawing::create([
                'user_id'=>Auth::id(),
                'file_name'=>$imageName,
            ]);
            //There's a few different ways to look at this which might be part of a larger conversation, 
            // so I won't touch this part yet. But is this something specific to drawings? Or is the broadcast
            // of model creation something that will happen for lots of different models? Regardless, I would 
            // change the name from NewDrawing to something like DrawingCreationBroadcast, or something like that
            // NewDrawing gets confusing since its too close to new Drawing();
            event(new NewDrawing($drawing));
            return response('Drawing created!',200);
        }
     }
    

ImageMeta class

    /** For all classes assume property declarations, and getters are included. 
    * Lean toward immutable classes where possible, so don't include setters 
    * unless it becomes necessary. Code re-use != object instance re-use.
    **/

    class ImageMeta {
        public function __construct($base64) {
            $this->image = str_replace('data:image/png;base64,', '', $base64);
            $this->imageName = 'draw'.time().'.png';
        }
    }

ImageArchiver class

    class ImageArchiver {
        private $config;
        
        public function __construct(ImageMeta $image) {
            $this->image = $image;
            $this->makeConfig();
        }
        
        private function makeConfig() {
            $this->config = [  
                'multipart' => [
                    [
                        'name' => 'file',
                        'contents' => $image->getImage()
                    ],
                    [
                        'name' => 'file_name',
                        'contents' => $imageName->getName()
                    ],
                    [
                        'name' => 'file_folder',
                        'contents' => 'drawings'
                    ],
                ],
            ]
        }
        
        //use type hinting here. Off the top of my head I don't know the parent class of guzzleClient
        public function archive($guzzleClient) {
            return $guzzleClient->post($this->config);
        }

    }
\$\endgroup\$
3
  • \$\begingroup\$ where should i store the ImageMeta and ImageArchiever class file ? \$\endgroup\$
    – Grey
    Commented Aug 24, 2020 at 14:45
  • \$\begingroup\$ my bad, so ur suggesting to use service right ? \$\endgroup\$
    – Grey
    Commented Aug 24, 2020 at 15:12
  • \$\begingroup\$ I'm not necessarily suggesting a service, but it might make sense depending on how often this is used. Put it where it makes sense. I don't just dump everything into a models folder, but use the Models folder as the root of all classes. \$\endgroup\$
    – Nick
    Commented Aug 24, 2020 at 18:40
1
\$\begingroup\$

In this presentation about cleaning up code Rafael Dohms talks about many ways to keep code lean - like avoiding the else keyword. (see the slides here).

It is wise to avoid the else keyword - especially when it isn't needed - e.g. when a previous block contains a return statement:

   if($response->getStatusCode() >= 500){
       return response($response->getBody(), $response->getStatusCode());
   }else{
       $drawing = Drawing::create([
           'user_id'=>Auth::id(),
           'file_name'=>$imageName,
       ]);
       event(new NewDrawing($drawing));
       return response('Drawing created!',200);
   }

In the Frontend code, it appears that images is set to an empty string:

 data(){
   return{
       images:'',
   }
},

Yet in the listen callback it is used like an array:

       Echo.channel('channel-drawing')
       .listen('NewDrawing', res => {
           this.images.push(res.drawing);
       })  

It should be initialized as an array in data().

Is the method listen() called anywhere other than the mounted() callback? If not, then it may be simpler just to move the code from that method into the mounted() callback.

\$\endgroup\$

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