4

Thank you very much for your help.

I am using Yii2. I want to use a REST POST request to send a list of words to the server, the server will translate the words and send back their translation.

When I send the POST request, I get this message in the browser JavaScript console: name "Bad Request" message "Missing required parameters: words" code 0 status 400 type "yii\web\BadRequestHttpException"

I am running PHP using this command: php yii serve.

JavaScript

window.onload = (event) => {
    document.querySelector("#translate").addEventListener("click", translate)
}

function translate() {
    const textarea = document.getElementById("words");
    const words = textarea.value.replace(/\r\n/g, "\n").split("\n");
    post('http://localhost:8080/index.php/rest-translation/translate', words)
}

async function post(url, words) {
    console.log(words)

    let options = {
        method: "POST",
        body: JSON.stringify({words: words})
    }

    const response = await fetch(url, options)
    if (response.ok) {
        response.json()
    }
}

This is the view:

<?php

use yii\helpers\Html;
use yii\widgets\DetailView;
use yii\widgets\ActiveForm;
use app\assets\TranslationAsset;

/** @var yii\web\View $this */
/** @var app\models\Book $book */
/** @var app\models\BookSection $section */

$this->title = $section->title;
$this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Section'), 'url' => ['book']];
$this->params['breadcrumbs'][] = $this->title;
\yii\web\YiiAsset::register($this);
?>
<div class="translate-section">

    <h1><?= Html::encode($this->title) ?></h1>

    <?= DetailView::widget([
        'model' => $book,
        'attributes' => [
            'id',
            'name',
        ],
    ]) ?>
    <?= DetailView::widget([
        'model' => $section,
        'attributes' => [
            'title',
        ],
    ]) ?>
    <div>
        <?php $form = ActiveForm::begin(); ?>
        <table>
            <tr>
                <td><textarea id="words"></textarea></td>
                <td><textarea id="first-translation"></textarea></td>
                <td><textarea id="second-translation"></textarea></td>
            </tr>
        </table>
        <button id="translate" type="button">Translate</button>

        <div class="form-group">
            <?= Html::submitButton(Yii::t('app', 'Save'), ['class' => 'btn btn-success']) ?>
        </div>

        <?php ActiveForm::end(); ?>

    </div>
    <?php TranslationAsset::register($this) ?>
</div>

This is the REST controller:

<?php

namespace app\controllers;

use app\models\Translation;
use yii\rest\ActiveController;

class RestTranslationController extends ActiveController
{
    public $modelClass = 'app\models\Translation';

    public function actionTranslate($words)
    {
        return  Translation::translate($words);
    }

    public function behaviors()
    {
        $behaviors = parent::behaviors();

        // remove authentication filter
        $auth = $behaviors['authenticator'];
        unset($behaviors['authenticator']);

        // add CORS filter
        $behaviors['corsFilter'] = [
            'class' => \yii\filters\Cors::class,
        ];

        // re-add authentication filter
        $behaviors['authenticator'] = $auth;
        // avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
        $behaviors['authenticator']['except'] = ['options'];

        return $behaviors;
    }
}

This is the model:

<?php

namespace app\models;

use Yii;

/**
 * This is the model class for table "translation".
 *
 * @property int $id
 * @property int $sourceEntryId
 * @property string $languageId
 * @property string $typeId
 * @property int $translationEntryId
 */
class Translation extends \yii\db\ActiveRecord
{
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return 'translation';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['sourceEntryId', 'languageId', 'typeId', 'translationEntryId'], 'required'],
            [['sourceEntryId', 'translationEntryId'], 'integer'],
            [['languageId', 'typeId'], 'string', 'max' => 10],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('app', 'ID'),
            'sourceEntryId' => Yii::t('app', 'Source Entry ID'),
            'languageId' => Yii::t('app', 'Language ID'),
            'typeId' => Yii::t('app', 'Type ID'),
            'translationEntryId' => Yii::t('app', 'Translation Entry ID'),
        ];
    }

    public static function translate($words)
    {
        //<TO DO>:Find and return the translation.
    }

    /**
     * {@inheritdoc}
     * @return TranslationQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new TranslationQuery(get_called_class());
    }
}

config/web.php

<?php

$params = require __DIR__ . '/params.php';
$db = require __DIR__ . '/db.php';

$config = [
    'id' => 'basic',
    'basePath' => dirname(__DIR__),
    'bootstrap' => ['log'],
    'aliases' => [
        '@bower' => '@vendor/bower-asset',
        '@npm'   => '@vendor/npm-asset',
    ],
    'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => 'xxxxxxxx',
            'parsers' => [
                'application/json' => 'yii\web\JsonParser',
            ]
        ],
        /*'response' => [
            'format' => yii\web\Response::FORMAT_JSON,
            'charset' => 'UTF-8',
            // ...
        ],*/
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
        'user' => [
            'identityClass' => 'app\models\User',
            'enableAutoLogin' => true,
        ],
        'errorHandler' => [
            'errorAction' => 'site/error',
        ],
        'mailer' => [
            'class' => \yii\symfonymailer\Mailer::class,
            'viewPath' => '@app/mail',
            // send all mails to a file by default.
            'useFileTransport' => true,
        ],
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
        'db' => $db,
        
        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => true,
            'enableStrictParsing' => false
        ]
    ],
    'params' => $params,
];

if (YII_ENV_DEV) {
    // configuration adjustments for 'dev' environment
    $config['bootstrap'][] = 'debug';
    $config['modules']['debug'] = [
        'class' => 'yii\debug\Module',
        // uncomment the following to add your IP if you are not connecting from localhost.
        //'allowedIPs' => ['127.0.0.1', '::1'],
    ];

    $config['bootstrap'][] = 'gii';
    $config['modules']['gii'] = [
        'class' => 'yii\gii\Module',
        // uncomment the following to add your IP if you are not connecting from localhost.
        //'allowedIPs' => ['127.0.0.1', '::1'],
    ];
}

return $config;

My environment: PHP 8.1.2-1ubuntu2.14 (cli) (built: Aug 18 2023 11:41:11) (NTS) Zend Engine v4.1.2 with Zend OPcache v8.1.2-1ubuntu2.14 with Xdebug v3.2.1 Yii2 (2.0.48.1)

Thank you very much for your help.

I googled for a solution, but I didn't find a anything.

5
  • Does the post (which is likely the only code you did NOT paste here) handle a JavaScript array? What do you see if you add console.log(words) just before the post? How does the function post look like? Commented Nov 7, 2023 at 6:58
  • You are sending a JSON-encoded numerically indexed array as your request body, you did not include a parameter name anywhere. You should probably pass { words: words } to your post method. Commented Nov 7, 2023 at 8:08
  • @mplungjan Thank you very much. I pasted the JavaScript code. This is what I see when I add console.log(words) Array(3) [ "dog", "cat", "bear" ]. Commented Nov 8, 2023 at 3:02
  • @CBroe Thank you very much. I changed the JavaScript code and pass {words: words}, but it doesn't work. Commented Nov 8, 2023 at 3:05
  • @mplungjan and CBroe. Thank you both. The problem has been solved by user Keyboard Corporation. Commented Nov 8, 2023 at 4:10

2 Answers 2

1

Answering this..

"Bad Request (#400): Missing required parameters: words"

Yes, you're sending words, as I see in your js, so it might be your Yii2 how it handle the request.

As I check your controller RestTranslationController, the actionTranslate($words) expecting your words parameter, but in your POST request, parameters are usually sent in the body, not in url.

This is how I come up that this is causing you the error. Can you do modified it to this?

public function actionTranslate()
{
   $words = Yii::$app->request->post('words');
   return Translation::translate($words);
}

We use Yii::$app->request->post('words') here to ensure that your controller will get the parameter(words) and hoping it will not causing you Bad Request error.

Edit

public function actionTranslate()
{
  $request_raw = file_get_contents('php://input');
  $request = json_decode($request_raw, true);
  $words = $request['words'];
  return Translation::translate($words);
}

Used php://input to read raw data from the request body.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much. I tried it, but it didn't work, it returns null. Apparently PHP doesn't fill $_POST when we use a JavaScript fetch post request.
It works, thank you so very much. I was totally clueless about how to solve this problem. Thank you very much one more time.
0

Today I was reading the Yii documentation, specifically I was reading the handle request section and I came across how Yii process POST requests.

Here is an extract of the documentation:

When implementing RESTful APIs, you often need to retrieve parameters that are submitted via PUT, PATCH or other request methods. You can get these parameters by calling the yii\web\Request::getBodyParam() methods. For example,

$request = Yii::$app->request;

// returns all parameters
$params = $request->bodyParams;

// returns the parameter "id"
$param = $request->getBodyParam('id');

Info: Unlike GET parameters, parameters submitted via POST, PUT, PATCH etc. are sent in the request body. The request component will parse these parameters when you access them through the methods described above. You can customize the way how these parameters are parsed by configuring the yii\web\Request::$parsers property.
--End of extract

We also need to add this in the config/web.php file:

...
'components' => [
'request' => [
            'cookieValidationKey' => 'Your key',
            'parsers' => [
                'application/json' => 'yii\web\JsonParser',
            ]
        ],
...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.