Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

bestmomo's avatar

I think I'd do that in a sanitize method in form request. Because the goal is just to modify inputs.

On the other hand @opheliadesign I wonder about your authorize method. If you want only admins use the form just use a middleware on controller methods.

nolros's avatar

@cm and @JarekTkaczyk

I would appreciate your thoughts on this approach. I ask as I'm a little wary of constructor in Eloquent.

In model

    /**
     * @param MobilePhone $mobile
     */
    protected function setMobileAttribute(MobilePhone $mobile )
    {
        $this->attributes['mobile'] = $mobile->toString();
    }

    /**
     * @param MobilePhone $mobile
     */
    protected function setReceiveAttribute(MobilePhone $mobile )
    {
        $this->attributes['receive'] = $mobile->toString();
    }

Value object MobilePhone

class MobilePhone
{

    /**
     * @var string
     */
    private $value;


    /**
     * Assert mobile #
     *
     * @param $mobile
     */
    public function __construct($mobile)
    {

        if( (empty($mobile)) && (! preg_match("/^[0-9]{3}-[0-9]{4}-[0-9]{4}$/", $mobile) ) )
        {
            $this->value = $mobile;
        }
        else
        {
            $this->value = null;
        }

    }

    /**
     * Return the object as a string
     *
     * @return string
     */
    public function __toString()
    {
        return $this->value;
    }

    /**
     * Create a new instance from a native form
     *
     * @param mixed $native
     * @return ValueObject
     */
    public static function fromNative($native)
    {
        return new MobilePhone($native);
    }

    /**
     * Return the object as a string
     *
     * @return string
     */
    public function toString()
    {
        return $this->value;
    }

}

Receive sms

class ReceiveSms
{

    /**
     * @var string
     */
    private $value;

    /**
     * Assert receive sms
     *
     * @param   string $name
     * @return  mixed
     */
    public function __construct($name)
    {


        if( (empty($mobile)) && (! preg_match("/^[0-9]{3}-[0-9]{4}-[0-9]{4}$/", $mobile) ) )
        {
            $this->value = true;
        }
        else
        {
            $this->value = false;
        }
    }

    /**
     * Return the object as a string
     *
     * @return string
     */
    public function __toString()
    {
        return $this->value;
    }

    /**
     * Create a new instance from a native form
     *
     * @param mixed $native
     * @return ValueObject
     */
    public static function fromNative($native)
    {
        return new ReceiveSms($native);
    }


    /**
     * Return the object as a string
     *
     * @return string
     */
    public function toString()
    {
        return $this->value;
    }

}
2 likes
nolros's avatar

@blackbird if it does it will be the first time ... lol ... the answer will be from pmall and some of the other guys yes it works, but 400 lines of code later :) bestmo answer will be simplify ... lol

bobbybouwmann's avatar

Yea, I also would go for the simple approach but it's still cool to see how much different solutions you can find for such a small part ;)

JarekTkaczyk's avatar

@nolros Your constructors contain errors. First is additionaly checking for empty && NOT preg_match - should be other way around. Anyway, what are you asking about?

bestmomo's avatar

@nolros I like as you can find so complex solutions for simple problems but, it's always interesting to follow your mind :)

cm's avatar

@nolros The idea to wrap the phone number and receive_sms in a VO is actually pretty good in my opinion, although the implementation you suggested has redundancy (the regex) and lacks portability (the same regex cannot be used for e. g. Controller validation).

@bestmomo

I like as you can find so complex solutions for simple problems

I'm a bit surprised that you call this a "simple problem". I actually think that the problem in this thread is very complex and that I haven't seen a proper way to do these things. I wish Jeff would make a video tutorial about this.

Sure, a hackish solution is easy, but if we want to have a clean solution

  • without side effects
  • without writing code twice
  • which maintains integrity for our business model in various edge cases
  • which is re-usable in other areas of our code (e. g. in every other model) then it's actually pretty tough I think. The depth of this problem isn't entirely clear because the example seems so easy.

I challenge you guys to come up with a proper solution for this Model and then you guys tell me if this is an easy thing or not:

class MathEquation {

    protected $formula;

    protected $result;

    protected $manual;

}

The constraints are very simple:

  • If you set $formula, you update $result with the result of the calculation and set $manual to false. You can use an object like FormulaEvaluator that provides the correct result for a formula string.
  • But if you set $result, this means that the user wants to override the calculated result with his own result. Hence, we set $manual to true.
  • Also, a $result cannot be set manually if $equation is null.
  • Note: It is allowed to set a "wrong" result by the user. Think of it like some sort of Math test or whatever

Use cases:

// Case 1: set things after each other
$math = new MathEquation();
$math->equation = '3*3'; // also sets the result to 9 and "manual=false"
$math->result = 5; // override the calculated result and mark as "manual=true"
$math->save();

// Case 2: Like in the phone example
MathEquation::create(Request::all());

// Case 3: Don't set everything
$math = new MathEquation();
$math->result = 5; // should then be set to null, because there is no equation
$math->save();

// Case 4: What about this baby? Order does matter!
$math = new MathEquation();
$math->result = 5;
$math->equation = '3*3';
$math->save();

In essence, this is no more complex than the phone example. Just setting a bunch of things when other things are set or unset. But maybe after all, it simply cannot be done with Mutators. I'm curious how you guys would solve this.

1 like
cm's avatar

I made an image to help you understand what a mutator really is:

8 likes
JarekTkaczyk's avatar

@cm Well, It's much bigger than the problem we had here in the beginning.

Original post was about how to set attribute in mutator for another attribute - and that's simple and straightforward.

Now, the problem that you can find here is, whether you want to just turn off X along with Y (like above, simple and straightforward) OR you want to design something bigger depending on your domain - here things can go in many different directions and there is no one, not two - there are many ways of reaching the goal.

So, back to the root: does mutator set receive_sms to null when phone_mobile is being set to null - obviously it does. Is it the way you want to control whether your client wants to receive the sms? I doubt.

I would see it this way (but again, it depends on the domain only): receive_sms is a setting, independent from the phone_mobile. Sending the sms is an action that depends on both - it will work if (phone_mobile && receive_sms).

Turning off receive_sms along with the phone_mobile isn't good idea in my opinion - a use could remove his phone no, for a day, then provide another one, and it probably wouldn't (shouldn't) affect whether he wants to receive some notification or not. It is obvious that he won't receive it unless he gave you his number.

That said, your Math example can be tackled differently as well. It is rather far from Eloquent model, so I wouldn't compare the two. Obviously some math calculations are actions that the object should perform, and you'd rather not use anything like $math->result = whatever - it makes no sense at all.

bestmomo's avatar

I like simple code and I think model must not know about fields relations. We just have to say to a model "Hey ! Keep these inputs for next time I need them !". But a model can format some attributes. For example encrypt password :

public function setPasswordAttribute($value)
{
    $this->attributes['password'] = bcrypt($value);
}

It's ok for this kind of action. But what about some fields relation ? "If this attribute is empty I need this other attribute null". I dont think model must wonder about that.

I think we must send to model a nice inputs array, and it's validation task to do that.

So what I'd do to achieve the present situation is only in form request. Something like this :

<?php namespace App\Http\Requests;

use App\Http\Requests\Request;
use Illuminate\Validation\Factory;

class UserRequest extends Request {

    protected $rules = [
        'first_name' => 'required',
        'last_name' => 'required',
        'email' => ['required', 'email'],
        'phone_mobile' => ['regex:/^[0-9]{3}-[0-9]{3}-[0-9]{4}$/']
    ];

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return $this->user()->hasRole('admin');
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        $id = $this->input('id');
        $rules = $this->rules;

        if ($id)
        {
            $rules['email'][] = "unique:users,email,$id";
            $rules['phone_mobile'][] = "unique:users,phone_mobile,$id";
        } else {
            $rules['email'][] = "unique:users";
            $rules['phone_mobile'][] = "unique:users";
        }

        return $rules;
    }

    public function fielsdIntegrity()
    {
        if(empty($this->input('phone_mobile')))
        {
            $this->merge(['receive_sms' => null]);   
        }

        return $this->all();
    }

    public function validator(Factory $factory)
    {
        return $factory->make(
            $this->fielsdIntegrity(), $this->container->call([$this, 'rules']), $this->messages()
        );
    }
}

And for mutator just manage one attribute :

public function setPhoneMobileAttribute($phone)
{
    $this->attributes['phone_mobile'] = empty($phone)? null : preg_replace('/\D/', '', $phone);            
}
cm's avatar

@bestmomo

I think we must send to model a nice inputs array, and it's validation task to do that.

Well, you have to do both, form validation and model (or domain) validation. See this thread: https://laracasts.com/discuss/channels/general-discussion/form-validation-or-model-validation

What would you do if you created a User model outside of an HttpRequest, e. g. as part of an artisan command? Then you wouldn't have any integrity checks. Either your command must do the same validation again (you'd have to type the same code again), or your model would go unvalidated into the database, which means that receive_sms isn't set properly or at least differs from when it was set via a Form.

Your solution certainly works, but it's not perfect for bigger apps in a more complex environment.

JarekTkaczyk's avatar

@cm That's why I prefer validation as service, that you can reuse wherever you need. I don't like whole idea of self validating FormRequest. It definitely has Wooow effect and is nice for rapid development & simple http apps, but not the best idea for anything bigger.

On the other hand, having validation in both model and somewhere else is not good either. It forces you to do it twice. Validation most likely covers 2 layers - business and data integrity. That said, I would agree that the latter may belong to the Model, but business rules don't. Business rules are domain thing and the domain doesn't care about data persistence engine, but data integrity might be specific to the engine.

cm's avatar

@JarekTkaczyk

On the other hand, having validation in both model and somewhere else is not good either. It forces you to do it twice.

Yes, but I'm afraid it is necessary in some cases. The easiest example would be this: Your User model requires a password. But your FormRequest requires that the user entered the password twice and that there's a password_confirmation. So, now your rules for validating the form are already a bit different from the rules validating the model. If you set up a user via an artisan command or by any other means, you don't need a matching password. You simply set one.

Another example would be: Your form has 3 selects: day, month, year. Your form checks whether all of these are valid. However, if they are valid, they get merged into a DateTime object. The model accepts only a DateTime object. Again, form validation is different from model validation in this case.

To make it more complicated: Say, you use an object merely as a DTO and don't persist it. Then, you cannot hook into the model's save() method to perform validation. But to maintain some sort of integrity, you need rules that are checked. Maybe we need a factory that spits out a valid model and it's only possible to instantiate things via this factory. Doesn't feel totally right to me, though.

Btw. I'm still looking for a satisfying solution myself.

JarekTkaczyk's avatar

@cm I get your point, but still required password is business rule.

Next, the DateTime example - again to satisfy model/data rules you need just this:

$dateString = $input->get('year') . '-' . $input->get('month') . '-' . $input->get('day');
$date = DateTime::createFromFormat('Y-m-d', $dateString);

This date will be perfectly valid in terms of data integrity but might be useless for the business

>>> DateTime::createFromFormat('Y-m-d', '1234-13-35');
=> <DateTime #0000000065e8465c000000005e6c7d7c> {
       date: "1235-02-04 15:18:28.000000",
       timezone_type: 3,
       timezone: "Europe/Warsaw"
   }

so you need business validation.

As to the DTO - Eloquent is not DTO but AR, and these are 2 separate subjects, so I would not use it as DTO.

Only validation that I would see in the model is making sure that persistence layer constraints will be satisfied. Eg. check string length, foreign key constraints etc.

bestmomo's avatar

@cm I get your point that a bigger application would need a deep reflexion about validation.

opheliadesign's avatar

Hey gang, looks like I asked a good question!

I like @bestmomo's Form Controller approach, I am going to give that a try tomorrow. Also, @cm, loved the Ninja Turtles, LOL!

Got thrown off by a few inches of snow here in Virginia so I'm just taking the day off.

I am closely monitoring this thread and really appreciate all of the great feedback, would be awesome if Jeffery did a video on this sort of scenario.

nolros's avatar

@bestmomo @cm @JarekTkaczyk @opheliadesign

I know I'm preaching to the choir here, but I don't know why you think this is more complex? It is OO programming with reusable code. Agree that is not well suited to smaller applications, but if you are working on a med to large app predictability and repeatability are vital. Also if you just write like this always then there is some overhead in setup but you make up for itt on the backend.

Jarek, sorry it was 2 am and I should never write code that late. In fact I should just never write code :)

Btw, CM, loved the ninja turtle diagram, but the analogy is slightly wrong :) In OOP it would be extract 4 DNA samples from from the same turtle put into 4 drums of a mutation slime you end up with 4 different ninja turtles. Same DNA from a single turtle, 4 different ninja turtles. If you inject the DNA, in this case the mobile phone number into different value objects (drums of slime), you will get different ninja turtles i.e. value objects. The object, or turtle, will contain the mutated value not the drum or DNA. Once you inject the mobile phone number in any amount of value objects the phone number is nolonger the number but a mutated version i.e. value objects will take that value and create a different instance in this case mobile phone and receive, but you could have n generated with the same value. i.e

$Leonardo->swordStrike()
$michelangelo->staffStrike(); // or whatever his weapon is
$raphael->ninjaStarStrike();

//not
$drum->kickass();
$turtle->kickass();
$DNA->kickass();

(man I stretched that analogy as far as it would go )

Here is the example. I'm not going to add the value objects back. They need some tweaks, but the concept remains the same so reference above.

In your controller, command or registration service you instantiate the value objects. The value object (not included in this post, but in my previous) are basic string examples, but you could create a phone type, Boolean type, etc. Yes I could add the magic methods as abstract class and make the code more efficient, but stick with the concept for a second.

Once I have a value object setup I can use the input $mobilePhoneNumber as the common denominator for any value object phone number dependencies that will mutate the number into some other value. In this case two, but like I said you could have n.

    // let say in my controller I would get the $phoneNumber and pass it to the VO
    $mobilePhoneNumber = $request->only('mobile_number');

    $setMobile       = new MobilePhone($mobilePhoneNumber);  
    $setReceive     = new ReceiveSms($mobilePhoneNumber);

// but could have as many of these as I need 
   $setYetAnotherDependency = new ValueObject($mobilePhoneNumber);
  1. Inside of my model I could have a registration method where I would inject these. Let's say a registration method
     // a registration method as an example 
     // passing the two values but you could inject other username, email, etc
    User::register($setMobile, $setReceive, $setYetAnotherDependency);
    
    public function register(MobilePhone $mobileNumber, ReceiveSms $setReceive, ValueObject $setYetAnotherDependency)
    {
        $user = new User();
        
        $user->mobile       = $mobileNumber;
        $user->receive     = $setReceive;
        $user->value         = $setYetAnotherDependency;
        
        return $user;
    }

  1. Inside of the same model I would have a mutators for each of my dependencies
protected function setMobileAttribute(MobilePhone $mobile )
{
    $this->attributes['mobile'] = $mobile->toString();
}

protected function setReceiveAttribute(ReceiveSms $receive )
{
    $this->attributes['receive'] = $receive->toString();
}

protected function setAnotherMobileAttribute(ValueObject $value )
{
    $this->attributes['value'] = $value->toString();
}

The advantage of this approach is I now have Value Objects for mobile phone and receive, etc. that I can inject anywhere in my application.

Simple example as I cannot think of better one :) A specification pattern to check for international numbers.

class PhoneNumberIsInternational implements PhoneNumberSpecification
{

    /**
     * @var UserRepository
     */
    private $repository;

    public function __construct(UserRepository $repository)
    {
        $this->repository = $repository;
    }

    /**
     * Check to see if the specification is satisfied
     *
     * @param   MobilePhone $mobile
     * @return  bool
     */
    public function isSatisfiedBy(MobilePhone $mobile)
    {

        // some regex that checks for international numbers in user or other repo
        if ($this->repository->phoneNumberIsInternational($mobile))
        {
            return true;
        }

        return false;
    }


}

I could also use these in my validation

class ExampleRegisterRequest extends Request {


    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {

        // simple example without any check etc. 
        $setRule = new MobilePhone( self::checkPhoneIsInternational(Request::get('mobile_number')));
        
        return [
            'mobile_phone'              => $setRule,
        ];
    }

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return Auth::check();
    }
    
    private static function checkPhoneIsInternational(MobilePhone $mobileNumber)
    {
        $specification = new PhoneNumberIsInternational($mobileNumber);

        if($specification->isSatisfiedBy($mobileNumber)) {
            return 'international regex '; 
        }
        else
        {
            return 'us phone regex';
        }
    }
    

}

A lot more than just mutator, but that was my point in an a 1000 word description :)

Previous

Please or to participate in this conversation.